From 9e5292a00323c0a80e0f02c629e06f738884ee3e Mon Sep 17 00:00:00 2001 From: Stefan Eissing Date: Tue, 1 Sep 2026 17:50:05 +0200 Subject: [PATCH 001/481] ngtcp2: shrink max sendbuf The sendbuf for a HTTP/3 stream in ngtcp2 could grow up to 10MB which can lead to large memory consumption without any real gains. Shrink the max size to 640KB, because that is enough for everyone. Closes #22786 --- lib/vquic/cf-ngtcp2-cmn.h | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/vquic/cf-ngtcp2-cmn.h b/lib/vquic/cf-ngtcp2-cmn.h index e0c27a95f7c0..fa71cfddc7c9 100644 --- a/lib/vquic/cf-ngtcp2-cmn.h +++ b/lib/vquic/cf-ngtcp2-cmn.h @@ -83,14 +83,13 @@ struct cf_quic_ctx; #if H3_STREAM_CHUNK_SIZE < NGTCP2_MAX_UDP_PAYLOAD_SIZE #error H3_STREAM_CHUNK_SIZE smaller than NGTCP2_MAX_UDP_PAYLOAD_SIZE #endif -/* The pool keeps spares around and half of a full stream window - * seems good. More does not seem to improve performance. +/* The pool keeps spares around for all streams. * The benefit of the pool is that stream buffers do not keep * spares. Memory consumption goes down when streams run empty, * have a large upload done, etc. */ -#define H3_STREAM_POOL_SPARES 2 +#define H3_STREAM_POOL_SPARES 5 /* The max amount of un-acked upload data we keep around per stream */ -#define H3_STREAM_SEND_BUFFER_MAX (10 * 1024 * 1024) +#define H3_STREAM_SEND_BUFFER_MAX (10 * H3_STREAM_CHUNK_SIZE) #define H3_STREAM_SEND_CHUNKS \ (H3_STREAM_SEND_BUFFER_MAX / H3_STREAM_CHUNK_SIZE) /* How much data we initially want to buffer un-acked */ From 717ebe4d0af0959ef26fbdf0b6d8c54dcdc32509 Mon Sep 17 00:00:00 2001 From: David Benjamin Date: Tue, 1 Sep 2026 09:03:23 -0400 Subject: [PATCH 002/481] vtls: fix Curl_cert_hostcheck when the pattern is not a C string https://github.com/curl/curl/pull/22767 removed some of the strlen calls, but it wasn't quite sufficient. Curl_cert_hostcheck has a number of places where it implicitly assumes the buffer is zero-terminated: - The check for empty strings assume that, even if the buffer is zero-length, it can be dereferenced. This only works for C strings where there's always a zero byte in there. Replace this with a length check. - The strncmp("*.") call will read past the buffer if the pattern equals "*". This works for C strings because it will stop at the zero byte first. For general buffers, this needs a length check first. The various checks for the first and last character are actually OK, because the caller rejects empty strings first, but I similarly guarded them for completeness. Likewise, I guarded the accesses of hostname in hopes it can eventually be de-C-string-ified, but the call to Curl_host_is_ipnum requires it to be a C string. Update unit1397 to cover these cases. Run each test with a separate-allocated buffer. The two fixed invalid accesses are then caught by ASan and valgrind. Closes #22795 --- lib/vtls/hostcheck.c | 12 +++++++----- tests/unit/unit1397.c | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+), 5 deletions(-) diff --git a/lib/vtls/hostcheck.c b/lib/vtls/hostcheck.c index 0af540993ffd..8fed51f6e934 100644 --- a/lib/vtls/hostcheck.c +++ b/lib/vtls/hostcheck.c @@ -66,6 +66,8 @@ static bool pmatch(const char *hostname, size_t hostlen, * Only match on "*" being used for the leftmost label, not "a*", "a*b" nor * "*b". * + * The pattern string does not necessarily need to be NUL-terminated. + * * Return TRUE on a match. FALSE if not. * * @unittest: 1397 @@ -84,16 +86,16 @@ static bool hostmatch(const char *hostname, DEBUGASSERT(hostlen); /* normalize pattern and hostname by stripping off trailing dots */ - if(hostname[hostlen - 1] == '.') + if(hostlen > 0 && hostname[hostlen - 1] == '.') hostlen--; - if(pattern[patternlen - 1] == '.') + if(patternlen > 0 && pattern[patternlen - 1] == '.') patternlen--; - if(strncmp(pattern, "*.", 2)) + if(patternlen > 2 && strncmp(pattern, "*.", 2)) return pmatch(hostname, hostlen, pattern, patternlen); /* detect host as IP address or starting with a dot and fail if so */ - else if(Curl_host_is_ipnum(hostname) || (hostname[0] == '.')) + else if(Curl_host_is_ipnum(hostname) || (hostlen > 0 && hostname[0] == '.')) return FALSE; /* We require at least 2 dots in the pattern to avoid too wide wildcard @@ -120,7 +122,7 @@ static bool hostmatch(const char *hostname, bool Curl_cert_hostcheck(const char *match, size_t matchlen, const char *hostname, size_t hostlen) { - if(match && *match && hostname && *hostname) + if(matchlen > 0 && hostlen > 0) return hostmatch(hostname, hostlen, match, matchlen); return FALSE; } diff --git a/tests/unit/unit1397.c b/tests/unit/unit1397.c index b133f2cc995b..4ec640878b5e 100644 --- a/tests/unit/unit1397.c +++ b/tests/unit/unit1397.c @@ -22,6 +22,7 @@ * ***************************************************************************/ #include "unitcheck.h" +#include "curlx/strdup.h" #include "vtls/hostcheck.h" static CURLcode test_unit1397(const char *arg) @@ -45,6 +46,7 @@ static CURLcode test_unit1397(const char *arg) { "", "b", FALSE }, { "a", "b", FALSE }, { "aa", "bb", FALSE }, + { "a", "*", FALSE }, { "\xff", "\xff", TRUE }, { "aa.aa.aa", "aa.aa.bb", FALSE }, { "aa.aa.aa", "aa.aa.aa", TRUE }, @@ -94,6 +96,8 @@ static CURLcode test_unit1397(const char *arg) int i; for(i = 0; tests[i].host; i++) { + size_t pattern_len; + char *pattern; if(tests[i].match != Curl_cert_hostcheck(tests[i].pattern, strlen(tests[i].pattern), tests[i].host, @@ -107,6 +111,38 @@ static CURLcode test_unit1397(const char *arg) tests[i].match ? "NOT " : ""); unitfail++; } + + /* Curl_cert_hostcheck() should not rely on trailing NULs in the pattern. + * The underlying TLS library might not make a NUL-terminated copy of the + * pattern. Copy it to a new buffer so ASan and valgrind can flag invalid + * accesses. */ + pattern_len = strlen(tests[i].pattern); + pattern = pattern_len ? curlx_memdup(tests[i].pattern, pattern_len) : NULL; + abort_unless(pattern_len == 0 || pattern, "Out of memory"); + if(tests[i].match != Curl_cert_hostcheck(pattern, + pattern_len, + tests[i].host, + strlen(tests[i].host))) { + curl_mfprintf(stderr, + "HOST: %s\n" + "PTRN: %s\n" + "did %sMATCH\n", + tests[i].host, + tests[i].pattern, + tests[i].match ? "NOT " : ""); + unitfail++; + } + curlx_free(pattern); + } + + /* Embedded NULs in the pattern should not be misinterpreted. */ + if(Curl_cert_hostcheck("foo\0bar", 7, "foo", 3)) { + curl_mfprintf(stderr, "foo\\0bar incorrectly matched foo\n"); + unitfail++; + } + if(Curl_cert_hostcheck("foo\0bar", 7, "foo.bar", 7)) { + curl_mfprintf(stderr, "foo\\0bar incorrectly matched foo.bar\n"); + unitfail++; } #endif From 5bbff5c2e9f78db0247a9897fae873bdff6a4be8 Mon Sep 17 00:00:00 2001 From: Max Dymond Date: Tue, 1 Sep 2026 21:02:57 +0100 Subject: [PATCH 003/481] doh: reject undersized HTTPS RDATA HTTPS RDATA requires at least two bytes for SvcPriority and one byte for TargetName. Reject shorter records before storing them to avoid zero-sized allocations in debug builds. Add unit coverage for RDATA lengths zero through three. Closes #22791 --- lib/vdns/doh.c | 10 ++++------ tests/unit/unit1650.c | 30 ++++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 6 deletions(-) diff --git a/lib/vdns/doh.c b/lib/vdns/doh.c index 3b52ed92f362..3873c2459fe8 100644 --- a/lib/vdns/doh.c +++ b/lib/vdns/doh.c @@ -600,12 +600,10 @@ static DOHcode doh_rdata(const unsigned char *doh, doh_store_aaaa(doh, index, d); break; #ifdef USE_HTTPSRR - case CURL_DNS_TYPE_HTTPS: { - DOHcode rc = doh_store_https(doh, index, d, rdlength); - if(rc) - return rc; - break; - } + case CURL_DNS_TYPE_HTTPS: + if(rdlength < 3) + return DOH_DNS_RDATA_LEN; + return doh_store_https(doh, index, d, rdlength); #endif default: /* unsupported type, or type we do not store, skip it */ diff --git a/tests/unit/unit1650.c b/tests/unit/unit1650.c index 890027b19353..2a9726959c12 100644 --- a/tests/unit/unit1650.c +++ b/tests/unit/unit1650.c @@ -229,6 +229,36 @@ static CURLcode test_unit1650(const char *arg) } } +#ifdef USE_HTTPSRR + { + unsigned char httpsrr[] = { + 0x00, 0x00, 0x81, 0x80, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x41, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00 + }; + + for(i = 0; i <= 3; i++) { + struct dohentry d; + DOHcode rc; + DOHcode expected = i < 3 ? DOH_DNS_RDATA_LEN : DOH_OK; + httpsrr[22] = (unsigned char)i; + de_init(&d); + rc = doh_resp_decode(httpsrr, sizeof(httpsrr) - 3 + i, + CURL_DNS_TYPE_HTTPS, &d); + if(rc != expected) { + curl_mfprintf(stderr, + "HTTPS RR length %zu: Expected return code %d got %d\n", + i, (int)expected, (int)rc); + abort_if(rc != expected, "return code"); + } + fail_unless(d.numhttps_rrs == (i == 3 ? 1 : 0), + "unexpected number of HTTPS RRs"); + de_cleanup(&d); + } + } +#endif + /* pass all sizes into the decoder until full */ for(i = 0; i < CURL_CSTRLEN(full49); i++) { struct dohentry d; From 94021cc30533e5d8be447571d473e9982f684525 Mon Sep 17 00:00:00 2001 From: David Benjamin Date: Wed, 2 Sep 2026 00:46:23 -0400 Subject: [PATCH 004/481] tests: Test certificates with embedded zero bytes The code removed from https://github.com/curl/curl/pull/22767 was part of a mess from when cURL once that strings in certificates were C strings and did not have embedded zeros. See these historical commits: * c0e8bed5bf7a7e56897e492a4dcc399621939995 * 0b66efac9cb0b606d943870237f689d71ba798f7 * 781b82baf5e481964d67971848e4f363915ef789 Since it seems there was never a test for any of this, fill in some of these missing tests now. (I was working on a fix for the same strlen issue but never got around to uploading it. Since #22767 has since landed, I figure I may as well contribute the tests.) Closes #22794 --- tests/http/test_17_ssl_use.py | 26 ++++++++++++++++++++++++++ tests/http/testenv/env.py | 10 ++++++++++ 2 files changed, 36 insertions(+) diff --git a/tests/http/test_17_ssl_use.py b/tests/http/test_17_ssl_use.py index 70af06eff9a0..bbab6c847ed8 100644 --- a/tests/http/test_17_ssl_use.py +++ b/tests/http/test_17_ssl_use.py @@ -598,3 +598,29 @@ def test_17_22_capath_invalid(self, env: Env, httpd): ]) # CURLE_PEER_FAILED_VERIFICATION or CURLE_SSL_CACERT_BADFILE assert r.exit_code in [60, 77], f'{r.dump_logs()}' + + @pytest.mark.parametrize("proto", Env.http_protos()) + def test_17_23_embedded_zero(self, env: Env, proto, httpd, nghttpx): + httpd.set_domain1_cred_name('embedded-zero') + httpd.reload_if_config_changed() + if proto == 'h3': + nghttpx.set_cred_name('embedded-zero') + nghttpx.reload_if_config_changed() + curl = CurlClient(env=env) + url = f'https://{env.authority_for(env.domain1, proto)}/curltest/sslinfo' + r = curl.http_get(url=url, alpn_proto=proto) + # CURLE_SSL_CONNECT_ERROR or CURLE_PEER_FAILED_VERIFICATION + assert r.exit_code in [35, 60], f'{r.dump_logs()}' + + @pytest.mark.parametrize("proto", Env.http_protos()) + def test_17_24_embedded_zero_wildcard(self, env: Env, proto, httpd, nghttpx): + httpd.set_domain1_cred_name('embedded-zero-wildcard') + httpd.reload_if_config_changed() + if proto == 'h3': + nghttpx.set_cred_name('embedded-zero-wildcard') + nghttpx.reload_if_config_changed() + curl = CurlClient(env=env) + url = f'https://{env.authority_for(env.domain1, proto)}/curltest/sslinfo' + r = curl.http_get(url=url, alpn_proto=proto) + # CURLE_SSL_CONNECT_ERROR or CURLE_PEER_FAILED_VERIFICATION + assert r.exit_code in [35, 60], f'{r.dump_logs()}' diff --git a/tests/http/testenv/env.py b/tests/http/testenv/env.py index 9571907d8622..943f4d6480aa 100644 --- a/tests/http/testenv/env.py +++ b/tests/http/testenv/env.py @@ -218,6 +218,16 @@ def __init__( CertificateSpec(name="user1", client=True), ], ), + CertificateSpec( + name='embedded-zero', + domains=[f'{self.domain1}\0actually.{self.domain2}'], + key_type='rsa2048', + ), + CertificateSpec( + name='embedded-zero-wildcard', + domains=[f'*.{self.tld}\0actually.{self.domain2}'], + key_type='rsa2048', + ), ] self.openssl = "openssl" From fe67952d34b9582df3cdf95a7bf62ca9ced5ef09 Mon Sep 17 00:00:00 2001 From: Stefan Eissing Date: Wed, 2 Sep 2026 12:50:08 +0200 Subject: [PATCH 005/481] test1399: improve reliability Disconnect test1399 from the system clock and provide fixed timestamps for recording progress and checking recorded timer values. Fixes #22798 Reported-by: Christian Hesse Closes #22800 --- tests/unit/unit1399.c | 68 +++++++++++++++++++++---------------------- 1 file changed, 33 insertions(+), 35 deletions(-) diff --git a/tests/unit/unit1399.c b/tests/unit/unit1399.c index a2fb21acec69..e2d6948329a5 100644 --- a/tests/unit/unit1399.c +++ b/tests/unit/unit1399.c @@ -44,18 +44,6 @@ static void t1399_stop(struct Curl_easy *easy) curl_global_cleanup(); } -/* - * Invoke Curl_pgrsTime for TIMER_STARTSINGLE to trigger the behavior that - * manages startransfer, but fake the startsingle_us time for purposes - * of the test. - */ -static void fake_t_startsingle_time(struct Curl_easy *data, - int seconds_offset) -{ - Curl_pgrsTime(data, TIMER_STARTSINGLE); - data->progress.delta.startsingle_us = (seconds_offset * 1000 * 1000); -} - static bool usec_matches_seconds(timediff_t time_usec, int expected_seconds) { static int usec_magnitude = 1000000; @@ -89,40 +77,50 @@ static void expect_timer_seconds(struct Curl_easy *data, int seconds) static CURLcode test_unit1399(const char *arg) { struct Curl_easy *data; - struct curltime now = curlx_now(); + struct curltime now; UNITTEST_BEGIN(t1399_setup(&data)) data->multi = NULL; + now.tv_sec = 12345678; + now.tv_usec = 0; data->progress.now = now; - memset(&data->progress.delta, 0, sizeof(data->progress.delta)); - memset(&data->progress.total, 0, sizeof(data->progress.total)); - - data->progress.start.tv_sec = now.tv_sec - 2; - data->progress.start.tv_usec = now.tv_usec; - fake_t_startsingle_time(data, 0); - - Curl_pgrsTime(data, TIMER_NAMELOOKUP); - Curl_pgrsTime(data, TIMER_CONNECT); - Curl_pgrsTime(data, TIMER_APPCONNECT); - Curl_pgrsTime(data, TIMER_PRETRANSFER); - Curl_pgrsTime(data, TIMER_STARTTRANSFER); + Curl_pgrsStart(data, &now); + + /* Record the first request start */ + Curl_pgrsTimeWas(data, TIMER_STARTSINGLE, now); + /* Let 2 seconds pass */ + now.tv_sec += 2; + Curl_pgrsTimeWas(data, TIMER_NAMELOOKUP, now); + Curl_pgrsTimeWas(data, TIMER_CONNECT, now); + Curl_pgrsTimeWas(data, TIMER_APPCONNECT, now); + Curl_pgrsTimeWas(data, TIMER_PRETRANSFER, now); + Curl_pgrsTimeWas(data, TIMER_STARTTRANSFER, now); expect_timer_seconds(data, 2); - /* now simulate the redirect */ - data->progress.delta.startredirect_us = 1 * 1000 * 1000; - fake_t_startsingle_time(data, 1); - - Curl_pgrsTime(data, TIMER_NAMELOOKUP); - Curl_pgrsTime(data, TIMER_CONNECT); - Curl_pgrsTime(data, TIMER_APPCONNECT); - Curl_pgrsTime(data, TIMER_PRETRANSFER); + /* now simulate the redirect after one second + * and subsequent follow request start */ + now.tv_sec += 1; + Curl_pgrsTimeWas(data, TIMER_REDIRECT, now); + Curl_pgrsTimeWas(data, TIMER_STARTSINGLE, now); + + /* Let 1 second pass */ + now.tv_sec += 1; + Curl_pgrsTimeWas(data, TIMER_NAMELOOKUP, now); + Curl_pgrsTimeWas(data, TIMER_CONNECT, now); + Curl_pgrsTimeWas(data, TIMER_APPCONNECT, now); + Curl_pgrsTimeWas(data, TIMER_PRETRANSFER, now); + Curl_pgrsTimeWas(data, TIMER_STARTTRANSFER, now); /* ensure t_starttransfer is only set on the first invocation by attempting * to set it twice */ - Curl_pgrsTime(data, TIMER_STARTTRANSFER); - Curl_pgrsTime(data, TIMER_STARTTRANSFER); + now.tv_sec += 1; + Curl_pgrsTimeWas(data, TIMER_STARTTRANSFER, now); + Curl_pgrsTimeWas(data, TIMER_STARTTRANSFER, now); + /* Accumulated times are now 3 seconds: + * - 2 after 1st request start + * - 1 aftger 2nd requerst start */ expect_timer_seconds(data, 3); UNITTEST_END(t1399_stop(data)) From 5dbdacaa04c0d09077829dd252c5ee79b6185ff6 Mon Sep 17 00:00:00 2001 From: Un1q32 Date: Wed, 2 Sep 2026 02:49:25 -0400 Subject: [PATCH 006/481] vquic: fix preprocessor check for old Darwin versions Closes #22797 --- lib/vquic/vquic.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/vquic/vquic.c b/lib/vquic/vquic.c index bbc5777eac88..44103d3d6641 100644 --- a/lib/vquic/vquic.c +++ b/lib/vquic/vquic.c @@ -564,9 +564,12 @@ static size_t vquic_msghdr_get_udp_gro(struct msghdr *msg) } #endif /* (HAVE_SENDMMSG || HAVE_SENDMSG) && !HAVE_APPLE_MSG_X */ +/* IP_RECVTOS was added in the macOS 10.13 SDK */ #if (defined(HAVE_SENDMMSG) || defined(HAVE_SENDMSG) || \ defined(HAVE_APPLE_MSG_X)) && \ - (defined(IP_RECVTOS) || defined(IP_TOS)) && defined(IPTOS_ECN_MASK) + ((defined(__APPLE__) && defined(IP_RECVTOS)) || \ + (!defined(__APPLE__) && defined(IP_TOS))) && \ + defined(IPTOS_ECN_MASK) static uint8_t vquic_msghdr_get_ecn(struct msghdr *msg, int family) { struct cmsghdr *cmsg; From 3a2c261293c204589ec2410dafb001425389f03a Mon Sep 17 00:00:00 2001 From: Jay Satiro Date: Tue, 1 Sep 2026 14:16:23 -0400 Subject: [PATCH 007/481] CURLOPT_SHARE.md: remove bad explanation for cookie sharing - Remove it because the correct explanation is already documented in CURL_LOCK_DATA_COOKIE in CURLSHOPT_SHARE.md. Prior to this change the doc said if you share cookies that the cookie engine is enabled. That is no longer true since d0a7ee3f (precedes curl 7.67.0). Reported-by: Artem Prilutskiy Ref: https://curl.se/libcurl/c/CURLSHOPT_SHARE.html#CURLLOCKDATACOOKIE Fixes https://github.com/curl/curl/issues/22788 Closes https://github.com/curl/curl/pull/22790 --- docs/libcurl/opts/CURLOPT_SHARE.md | 5 ----- 1 file changed, 5 deletions(-) diff --git a/docs/libcurl/opts/CURLOPT_SHARE.md b/docs/libcurl/opts/CURLOPT_SHARE.md index 907e4f280519..da76087607b3 100644 --- a/docs/libcurl/opts/CURLOPT_SHARE.md +++ b/docs/libcurl/opts/CURLOPT_SHARE.md @@ -33,11 +33,6 @@ itself. This enables several curl handles to share data. If the curl handles are used simultaneously in multiple threads, you **must** use the locking methods in the share handle. See curl_share_setopt(3) for details. -If you add a share that is set to share cookies, your easy handle uses that -cookie cache and get the cookie engine enabled. If you stop sharing an object -that was using cookies (or change to another object that does not share -cookies), the easy handle gets its cookie engine disabled. - Data that the share object is not set to share is dealt with the usual way, as if no share was used. From c2921730a753577832502f89223edfa67420f5cf Mon Sep 17 00:00:00 2001 From: Daniel Stenberg Date: Wed, 2 Sep 2026 14:58:59 +0200 Subject: [PATCH 008/481] perf/base64: split into separate encoding plus decoding tests Closes #22802 --- tests/perf/Makefile.inc | 3 +- tests/perf/README.md | 20 ++++++-- tests/perf/base64dec.c | 72 ++++++++++++++++++++++++++++ tests/perf/{base64.c => base64enc.c} | 9 +--- 4 files changed, 90 insertions(+), 14 deletions(-) create mode 100644 tests/perf/base64dec.c rename tests/perf/{base64.c => base64enc.c} (89%) diff --git a/tests/perf/Makefile.inc b/tests/perf/Makefile.inc index 9660c9cb1c59..40198fd6f040 100644 --- a/tests/perf/Makefile.inc +++ b/tests/perf/Makefile.inc @@ -43,7 +43,8 @@ CURLX_C = \ # All performance test programs TESTS_C = \ - base64.c \ + base64dec.c \ + base64enc.c \ percent.c \ snprintf.c \ urlparser.c diff --git a/tests/perf/README.md b/tests/perf/README.md index def7bf91b2f4..db155233ca7f 100644 --- a/tests/perf/README.md +++ b/tests/perf/README.md @@ -24,13 +24,23 @@ them: `./perf urlparser URLs.txt [loops]` A test sample of 100K URLs can be found [here](https://raw.githubusercontent.com/ada-url/url-various-datasets/refs/heads/main/top100/top100.txt) -## `base64` +## `base64enc` -This test first base64 encodes a 256-byte buffer that has every different -byte octet represented. It then decodes that string. Repeatedly in a loop the -provided number of times: `./perf base64 [loops]` +This test base64 encodes a 256-byte buffer that has every different byte octet +represented. Repeatedly in a loop the provided number of times: + + ./perf base64enc [loops] + +## `base64dec` + +This test base64 decodes a big encoded string. Repeatedly in a loop the +provided number of times: + + ./perf base64dec [loops] ## `snprintf` This test repeatedly formats a representative string and measures the time per -call: `./perf snprintf [loops]` +call: + + ./perf snprintf [loops] diff --git a/tests/perf/base64dec.c b/tests/perf/base64dec.c new file mode 100644 index 000000000000..da4339758054 --- /dev/null +++ b/tests/perf/base64dec.c @@ -0,0 +1,72 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ +#include "first.h" + +/* this is 256 bytes using the octets 0-255, base64 encoded */ +#define BLOB64 \ + "AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4vMD" \ + "EyMzQ1Njc4OTo7PD0+P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV5fYGFi" \ + "Y2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6e3x9fn+AgYKDhIWGh4iJiouMjY6PkJGSk5" \ + "SVlpeYmZqbnJ2en6ChoqOkpaanqKmqq6ytrq+wsbKztLW2t7i5uru8vb6/wMHCw8TF" \ + "xsfIycrLzM3Oz9DR0tPU1dbX2Nna29zd3t/g4eLj5OXm5+jp6uvs7e7v8PHy8/T19v" \ + "f4+fr7/P3+/w==" + +static int test_base64dec(int argc, const char **argv) +{ + struct curltime start; + struct curltime end; + timediff_t us; + long long hn; + curl_off_t loops = 10000000, loop; + + if(argc > 1) { + const char *ptr = argv[1]; + curl_off_t num; + if(!curlx_str_number(&ptr, &num, CURL_OFF_T_MAX) && !*ptr) + loops = num; + } + + start = curlx_now(); + for(loop = 0; loop < loops; loop++) { + unsigned char *recoded = NULL; + size_t reclen; + CURLcode result = curlx_base64_decode(BLOB64, &recoded, &reclen); + curlx_free(recoded); + if(result) { + curl_mfprintf(stderr, "unexpected coding error: %d\n", (int)result); + return 1; + } + } + end = curlx_now(); + us = curlx_timediff_us(end, start); /* how many microseconds */ + hn = loop ? us * 100000 / loops : 0; /* 100 times too big */ + curl_mprintf("Loops: %" CURL_FORMAT_CURL_OFF_T "\n" + "Time: %lld usecs\n" + "Time/loop: %lld.%0lld ns\n", + loops, + (long long)us, + (long long)hn / 100, + (long long)hn % 100); + return 0; +} diff --git a/tests/perf/base64.c b/tests/perf/base64enc.c similarity index 89% rename from tests/perf/base64.c rename to tests/perf/base64enc.c index 585fa1d39962..602799185018 100644 --- a/tests/perf/base64.c +++ b/tests/perf/base64enc.c @@ -23,7 +23,7 @@ ***************************************************************************/ #include "first.h" -static int test_base64(int argc, const char **argv) +static int test_base64enc(int argc, const char **argv) { struct curltime start; struct curltime end; @@ -52,13 +52,6 @@ static int test_base64(int argc, const char **argv) size_t enclen; CURLcode result = curlx_base64_encode(array, sizeof(array), &encoded, &enclen); - if(!result) { - unsigned char *recoded = NULL; - size_t reclen; - /* now decode it again */ - result = curlx_base64_decode(encoded, &recoded, &reclen); - curlx_free(recoded); - } if(result) { curl_mfprintf(stderr, "unexpected coding error: %d\n", (int)result); return 1; From 6cc5a420b3f5e500ffde3ab7517a6624030191f0 Mon Sep 17 00:00:00 2001 From: Daniel Stenberg Date: Wed, 2 Sep 2026 23:03:39 +0200 Subject: [PATCH 009/481] clang-tidy: disable readability-redundant-parentheses It is an opinion and a judgement call I think is better left for humans. Ref: #22803 where this suddenly showed up for code not added in the PR. Closes #22809 --- .clang-tidy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.clang-tidy.yml b/.clang-tidy.yml index 3ea2c6a88a73..c804e86f9ffb 100644 --- a/.clang-tidy.yml +++ b/.clang-tidy.yml @@ -48,7 +48,7 @@ Checks: - readability-redundant-control-flow - readability-redundant-declaration - readability-redundant-function-ptr-dereference - - readability-redundant-parentheses + # - readability-redundant-parentheses - readability-redundant-preprocessor - readability-suspicious-call-argument - readability-uppercase-literal-suffix From ea3c0e962aee86f0d45bd01edde7b41663b811e9 Mon Sep 17 00:00:00 2001 From: Daniel Stenberg Date: Wed, 2 Sep 2026 15:06:44 +0200 Subject: [PATCH 010/481] tests/perf: split "percent" into urlencode + urldecode Closes #22803 --- tests/perf/Makefile.inc | 3 +- tests/perf/README.md | 14 +++++ tests/perf/urldecode.c | 85 +++++++++++++++++++++++++++ tests/perf/{percent.c => urlencode.c} | 15 +---- 4 files changed, 103 insertions(+), 14 deletions(-) create mode 100644 tests/perf/urldecode.c rename tests/perf/{percent.c => urlencode.c} (85%) diff --git a/tests/perf/Makefile.inc b/tests/perf/Makefile.inc index 40198fd6f040..fb1d8982184a 100644 --- a/tests/perf/Makefile.inc +++ b/tests/perf/Makefile.inc @@ -45,6 +45,7 @@ CURLX_C = \ TESTS_C = \ base64dec.c \ base64enc.c \ - percent.c \ snprintf.c \ + urldecode.c \ + urlencode.c \ urlparser.c diff --git a/tests/perf/README.md b/tests/perf/README.md index db155233ca7f..12bef815eae0 100644 --- a/tests/perf/README.md +++ b/tests/perf/README.md @@ -44,3 +44,17 @@ This test repeatedly formats a representative string and measures the time per call: ./perf snprintf [loops] + +## `urlencode` + +This test URL (percent) encodes a 256-byte buffer that has every different +byte octet represented. Repeatedly in a loop the provided number of times: + + ./perf urlencode [loops] + +## `urldecode` + +This test URL (percent) decodes a 600+ byte buffer. Repeatedly in a loop the +provided number of times: + + ./perf urldecode [loops] diff --git a/tests/perf/urldecode.c b/tests/perf/urldecode.c new file mode 100644 index 000000000000..b4b391e325f3 --- /dev/null +++ b/tests/perf/urldecode.c @@ -0,0 +1,85 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ +#include "first.h" + +/* every octet 0-255 URL encoded (or not). In two parts to avoid compilers + complaining */ +#define URLENCODED1 \ + "%00%01%02%03%04%05%06%07%08%09%0A%0B%0C%0D%0E%0F%10%11%12%13%14%15" \ + "%16%17%18%19%1A%1B%1C%1D%1E%1F%20%21%22%23%24%25%26%27%28%29%2A%2B" \ + "%2C-.%2F0123456789%3A%3B%3C%3D%3E%3F%40ABCDEFGHIJKLMNOPQRSTUVWXYZ" \ + "%5B%5C%5D%5E_%60abcdefghijklmnopqrstuvwxyz%7B%7C%7D~%7F%80%81%82%83" \ + "%84%85%86%87%88%89%8A%8B%8C%8D%8E%8F%90%91%92%93%94%95%96%97%98%99" + +#define URLENCODED2 \ + "%9A%9B%9C%9D%9E%9F%A0%A1%A2%A3%A4%A5%A6%A7%A8%A9%AA%AB%AC%AD%AE%AF" \ + "%B0%B1%B2%B3%B4%B5%B6%B7%B8%B9%BA%BB%BC%BD%BE%BF%C0%C1%C2%C3%C4%C5" \ + "%C6%C7%C8%C9%CA%CB%CC%CD%CE%CF%D0%D1%D2%D3%D4%D5%D6%D7%D8%D9%DA%DB" \ + "%DC%DD%DE%DF%E0%E1%E2%E3%E4%E5%E6%E7%E8%E9%EA%EB%EC%ED%EE%EF%F0%F1" \ + "%F2%F3%F4%F5%F6%F7%F8%F9%FA%FB%FC%FD%FE%FF" + +static int test_urldecode(int argc, const char **argv) +{ + struct curltime start; + struct curltime end; + timediff_t us; + long long hn; + curl_off_t loops = 2500000, loop; + char buffer[1024]; + int len; + + if(argc > 1) { + const char *ptr = argv[1]; + curl_off_t num; + if(!curlx_str_number(&ptr, &num, CURL_OFF_T_MAX) && !*ptr) + loops = num; + } + + len = curl_msnprintf(buffer, sizeof(buffer), + "%s%s", URLENCODED1, URLENCODED2); + + start = curlx_now(); + for(loop = 0; loop < loops; loop++) { + char *unesc = NULL; + int unlen = 0; + /* now unescape it again */ + unesc = curl_easy_unescape(NULL, buffer, len, &unlen); + if(!unesc) { + curl_mfprintf(stderr, "unexpected unescape error\n"); + return 1; + } + curl_free(unesc); + } + end = curlx_now(); + us = curlx_timediff_us(end, start); /* how many microseconds */ + hn = loop ? us * 100000 / loops : 0; /* 100 times too big */ + curl_mprintf("Loops: %" CURL_FORMAT_CURL_OFF_T "\n" + "Time: %lld usecs\n" + "Time/loop: %lld.%0lld ns\n", + loops, + (long long)us, + (long long)hn / 100, + (long long)hn % 100); + return 0; +} diff --git a/tests/perf/percent.c b/tests/perf/urlencode.c similarity index 85% rename from tests/perf/percent.c rename to tests/perf/urlencode.c index 345b78e1f122..999e0f25094d 100644 --- a/tests/perf/percent.c +++ b/tests/perf/urlencode.c @@ -23,7 +23,7 @@ ***************************************************************************/ #include "first.h" -static int test_percent(int argc, const char **argv) +static int test_urlencode(int argc, const char **argv) { struct curltime start; struct curltime end; @@ -47,18 +47,7 @@ static int test_percent(int argc, const char **argv) for(loop = 0; loop < loops; loop++) { char *encoded = curl_easy_escape(NULL, (char *)array, (int)sizeof(array)); - if(encoded) { - char *unesc = NULL; - int unlen = 0; - /* now unescape it again */ - unesc = curl_easy_unescape(NULL, encoded, 0, &unlen); - if(!unesc) { - curl_mfprintf(stderr, "unexpected unescape error\n"); - return 1; - } - curl_free(unesc); - } - else { + if(!encoded) { curl_mfprintf(stderr, "unexpected escape error\n"); return 1; } From 959cb7df1ca224e30855d29b3b7d44dff87df01a Mon Sep 17 00:00:00 2001 From: Roshan Ramani Date: Thu, 3 Sep 2026 11:14:06 +0530 Subject: [PATCH 011/481] docs: add the missing .html to the url-encode book link The everything.curl.dev page is served at url-encode.html; without the suffix it 404s. It is the only everything.curl.dev link in docs. Closes #22811 --- docs/MANUAL.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/MANUAL.md b/docs/MANUAL.md index d979455d8496..6f1cc2e90051 100644 --- a/docs/MANUAL.md +++ b/docs/MANUAL.md @@ -306,7 +306,7 @@ Post a simple `name` and `phone` guestbook. curl -d "name=Rafael%20Sagula&phone=3320780" https://www.example.com/guest.cgi -Or automatically [URL encode the data](https://everything.curl.dev/http/post/url-encode). +Or automatically [URL encode the data](https://everything.curl.dev/http/post/url-encode.html). curl --data-urlencode "name=Rafael Sagula&phone=3320780" https://www.example.com/guest.cgi From ed131747c1a3dc7f816d26724c44537ac8acba44 Mon Sep 17 00:00:00 2001 From: Stefan Eissing Date: Wed, 2 Sep 2026 12:25:50 +0200 Subject: [PATCH 012/481] x509: stricter parsing Check that the "validity" ASN.1 field has a length matching the contained date elements and fail the parsing otherwise. This is a precaution against malformed cert data that should have been caught by the TLS backend already. A bit added defense. Closes #22799 --- lib/vtls/gtls.c | 4 +- lib/vtls/mbedtls.c | 5 +- lib/vtls/rustls.c | 13 ++--- lib/vtls/schannel.c | 10 ++-- lib/vtls/wolfssl.c | 4 +- lib/vtls/x509asn1.c | 118 ++++++++++++++++++++++-------------------- lib/vtls/x509asn1.h | 16 +++--- tests/unit/unit1651.c | 6 +-- tests/unit/unit1657.c | 75 ++++++++++++++++++++++++--- tests/unit/unit1666.c | 10 ++-- tests/unit/unit1667.c | 2 +- tests/unit/unit1676.c | 6 +-- 12 files changed, 166 insertions(+), 103 deletions(-) diff --git a/lib/vtls/gtls.c b/lib/vtls/gtls.c index 9d17573dc5d1..4e4d42ec6d6d 100644 --- a/lib/vtls/gtls.c +++ b/lib/vtls/gtls.c @@ -1694,8 +1694,8 @@ CURLcode Curl_gtls_verifyserver(struct Curl_cfilter *cf, goto out; for(i = 0; i < chain.num_certs; i++) { - const char *beg = (const char *)chain.certs[i].data; - const char *end = beg + chain.certs[i].size; + const uint8_t *beg = chain.certs[i].data; + const uint8_t *end = beg + chain.certs[i].size; result = Curl_extract_certinfo(data, (int)i, beg, end); if(result) diff --git a/lib/vtls/mbedtls.c b/lib/vtls/mbedtls.c index dbee473d7dbb..3c83fe8f6e62 100644 --- a/lib/vtls/mbedtls.c +++ b/lib/vtls/mbedtls.c @@ -433,9 +433,8 @@ static void mbed_extract_certinfo(struct Curl_easy *data, result = Curl_ssl_init_certinfo(data, cert_count); for(i = 0, cur = crt; result == CURLE_OK && cur; ++i, cur = cur->next) { - const char *beg = (const char *)cur->raw.p; - const char *end = beg + cur->raw.len; - result = Curl_extract_certinfo(data, i, beg, end); + result = Curl_extract_certinfo(data, i, cur->raw.p, + cur->raw.p + cur->raw.len); } } diff --git a/lib/vtls/rustls.c b/lib/vtls/rustls.c index 9c0b0d7be3b3..c21989037581 100644 --- a/lib/vtls/rustls.c +++ b/lib/vtls/rustls.c @@ -1257,15 +1257,10 @@ static CURLcode cr_connect(struct Curl_cfilter *cf, struct Curl_easy *data, (int)errorlen, errorbuf); return map_error(rresult); } - { - const char *beg; - const char *end; - beg = (const char *)der_data; - end = (const char *)(der_data + der_len); - result = Curl_extract_certinfo(data, (int)i, beg, end); - if(result) - return result; - } + result = Curl_extract_certinfo(data, (int)i, + der_data, der_data + der_len); + if(result) + return result; } } diff --git a/lib/vtls/schannel.c b/lib/vtls/schannel.c index 34c6e79f79eb..9c73e1b2e18e 100644 --- a/lib/vtls/schannel.c +++ b/lib/vtls/schannel.c @@ -1118,7 +1118,7 @@ static CURLcode schannel_pkp_pin_peer_pubkey(struct Curl_cfilter *cf, do { SECURITY_STATUS sspi_status; - const char *x509_der; + uint8_t *x509_der; DWORD x509_der_len; struct Curl_X509certificate x509_parsed; struct Curl_asn1Element *pubkey; @@ -1139,7 +1139,7 @@ static CURLcode schannel_pkp_pin_peer_pubkey(struct Curl_cfilter *cf, (pCertContextServer->cbCertEncoded > 0))) break; - x509_der = (const char *)pCertContextServer->pbCertEncoded; + x509_der = (uint8_t *)pCertContextServer->pbCertEncoded; x509_der_len = pCertContextServer->cbCertEncoded; memset(&x509_parsed, 0, sizeof(x509_parsed)); if(Curl_parseX509(&x509_parsed, x509_der, x509_der + x509_der_len)) @@ -1552,12 +1552,12 @@ static bool add_cert_to_certinfo(const CERT_CONTEXT *ccert_context, struct Adder_args *args = (struct Adder_args *)raw_arg; args->result = CURLE_OK; if(valid_cert_encoding(ccert_context)) { - const char *beg = (const char *)ccert_context->pbCertEncoded; - const char *end = beg + ccert_context->cbCertEncoded; int insert_index = reverse_order ? (args->certs_count - 1) - args->idx : args->idx; args->result = Curl_extract_certinfo(args->data, insert_index, - beg, end); + ccert_context->pbCertEncoded, + ccert_context->pbCertEncoded + + ccert_context->cbCertEncoded); args->idx++; } return args->result == CURLE_OK; diff --git a/lib/vtls/wolfssl.c b/lib/vtls/wolfssl.c index 4b9facadf01e..03e118249b65 100644 --- a/lib/vtls/wolfssl.c +++ b/lib/vtls/wolfssl.c @@ -1589,7 +1589,7 @@ CURLcode Curl_wssl_verify_pinned(struct Curl_cfilter *cf, if(pinnedpubkey) { #ifdef KEEP_PEER_CERT - const char *x509_der; + const uint8_t *x509_der; int x509_der_len; struct Curl_X509certificate x509_parsed; struct Curl_asn1Element *pubkey; @@ -1602,7 +1602,7 @@ CURLcode Curl_wssl_verify_pinned(struct Curl_cfilter *cf, goto end; } - x509_der = (const char *)wolfSSL_X509_get_der(x509, &x509_der_len); + x509_der = wolfSSL_X509_get_der(x509, &x509_der_len); if(!x509_der) { failf(data, "SSL: failed retrieving ASN.1 server certificate"); goto end; diff --git a/lib/vtls/x509asn1.c b/lib/vtls/x509asn1.c index 4c5dac3e8b83..bf736701b550 100644 --- a/lib/vtls/x509asn1.c +++ b/lib/vtls/x509asn1.c @@ -127,11 +127,11 @@ static const struct Curl_OID OIDtable[] = { #define CURL_ASN1_MAX_RECURSIONS 16 -static const char *getASN1Element_(struct Curl_asn1Element *elem, - const char *beg, const char *end, - size_t lvl) +static const uint8_t *getASN1Element_(struct Curl_asn1Element *elem, + const uint8_t *beg, const uint8_t *end, + size_t lvl) { - unsigned char b; + uint8_t b; size_t len; struct Curl_asn1Element lelem; @@ -146,7 +146,7 @@ static const char *getASN1Element_(struct Curl_asn1Element *elem, /* Process header byte. */ elem->header = beg; - b = (unsigned char)*beg++; + b = *beg++; elem->constructed = (b & 0x20) != 0; elem->eclass = (b >> 6) & 3; b &= 0x1F; @@ -157,7 +157,7 @@ static const char *getASN1Element_(struct Curl_asn1Element *elem, /* Process length. */ if(beg >= end) return NULL; - b = (unsigned char)*beg++; + b = *beg++; if(!(b & 0x80)) len = b; else { @@ -178,7 +178,7 @@ static const char *getASN1Element_(struct Curl_asn1Element *elem, elem->end = beg; return beg + 1; } - else if((unsigned)b > (size_t)(end - beg)) + else if(b > (size_t)(end - beg)) return NULL; /* Does not fit in source. */ else { /* Get long length. */ @@ -186,7 +186,7 @@ static const char *getASN1Element_(struct Curl_asn1Element *elem, do { if(len & 0xFF000000L) return NULL; /* Lengths > 32 bits are not supported. */ - len = (len << 8) | (unsigned char) *beg++; + len = (len << 8) | *beg++; } while(--b); } } @@ -200,10 +200,10 @@ static const char *getASN1Element_(struct Curl_asn1Element *elem, /* * @unittest 1657 */ -UNITTEST const char *getASN1Element(struct Curl_asn1Element *elem, - const char *beg, const char *end); -UNITTEST const char *getASN1Element(struct Curl_asn1Element *elem, - const char *beg, const char *end) +UNITTEST const uint8_t *getASN1Element(struct Curl_asn1Element *elem, + const uint8_t *beg, const uint8_t *end); +UNITTEST const uint8_t *getASN1Element(struct Curl_asn1Element *elem, + const uint8_t *beg, const uint8_t *end) { return getASN1Element_(elem, beg, end, 0); } @@ -231,12 +231,12 @@ static const struct Curl_OID *searchOID(const char *oid) */ static CURLcode bool2str(struct dynbuf *store, - const char *beg, const char *end) + const uint8_t *beg, const uint8_t *end) { uint8_t val; if(end - beg != 1) return CURLE_BAD_FUNCTION_ARGUMENT; - val = (uint8_t)*beg; + val = *beg; if((val != 0xff) && (val != 0x00)) return CURLE_BAD_FUNCTION_ARGUMENT; return curlx_dyn_add(store, val ? "TRUE" : "FALSE"); @@ -248,22 +248,23 @@ static CURLcode bool2str(struct dynbuf *store, * Return error code. */ static CURLcode octet2str(struct dynbuf *store, - const char *beg, const char *end) + const uint8_t *beg, const uint8_t *end) { CURLcode result = CURLE_OK; while(!result && beg < end) - result = curlx_dyn_addf(store, "%02x:", (unsigned char)*beg++); + result = curlx_dyn_addf(store, "%02x:", *beg++); return result; } -static CURLcode bit2str(struct dynbuf *store, const char *beg, const char *end) +static CURLcode bit2str(struct dynbuf *store, + const uint8_t *beg, const uint8_t *end) { /* Convert an ASN.1 bit string to a printable string. */ if(beg < end) { - uint8_t unused_bits = (uint8_t)*beg++; + uint8_t unused_bits = *beg++; if(unused_bits > 7) return CURLE_BAD_FUNCTION_ARGUMENT; } @@ -277,13 +278,14 @@ static CURLcode bit2str(struct dynbuf *store, const char *beg, const char *end) * * Returns error. */ -static CURLcode int2str(struct dynbuf *store, const char *beg, const char *end) +static CURLcode int2str(struct dynbuf *store, + const uint8_t *beg, const uint8_t *end) { uint32_t uval = 0; int32_t sval = 0; size_t n = end - beg; size_t i; - const unsigned char *p = (const unsigned char *)beg; + const uint8_t *p = beg; if(!n) return CURLE_BAD_FUNCTION_ARGUMENT; @@ -315,8 +317,8 @@ static CURLcode int2str(struct dynbuf *store, const char *beg, const char *end) * * Returns error. */ -static CURLcode utf8asn1str(struct dynbuf *to, int type, const char *from, - const char *end) +static CURLcode utf8asn1str(struct dynbuf *to, int type, + const uint8_t *from, const uint8_t *end) { size_t inlength = end - from; int size = 1; @@ -357,14 +359,14 @@ static CURLcode utf8asn1str(struct dynbuf *to, int type, const char *from, switch(size) { case 4: - wc = (wc << 8) | *(const unsigned char *)from++; - wc = (wc << 8) | *(const unsigned char *)from++; + wc = (wc << 8) | *from++; + wc = (wc << 8) | *from++; FALLTHROUGH(); case 2: - wc = (wc << 8) | *(const unsigned char *)from++; + wc = (wc << 8) | *from++; FALLTHROUGH(); default: /* case 1: */ - wc = (wc << 8) | *(const unsigned char *)from++; + wc = (wc << 8) | *from++; } if(wc >= 0x00000080) { if(wc >= 0x00000800) { @@ -404,9 +406,9 @@ static CURLcode utf8asn1str(struct dynbuf *to, int type, const char *from, * @unittest 1666 */ UNITTEST CURLcode encodeOID(struct dynbuf *store, - const char *beg, const char *end); + const uint8_t *beg, const uint8_t *end); UNITTEST CURLcode encodeOID(struct dynbuf *store, - const char *beg, const char *end) + const uint8_t *beg, const uint8_t *end) { uint32_t x; uint32_t y; @@ -417,7 +419,7 @@ UNITTEST CURLcode encodeOID(struct dynbuf *store, /* Process the first two numbers. The initial digit cannot be larger than 2 */ - y = *(const unsigned char *)beg++; + y = *beg++; if(y <= 80) x = y / 40; else @@ -434,7 +436,7 @@ UNITTEST CURLcode encodeOID(struct dynbuf *store, do { if((t & 0xFE000000) || (beg >= end)) return CURLE_BAD_FUNCTION_ARGUMENT; - y = *(const unsigned char *)beg++; + y = *beg++; t = (t << 7) | (y & 0x7F); } while(y & 0x80); y = t; @@ -454,7 +456,7 @@ UNITTEST CURLcode encodeOID(struct dynbuf *store, return CURLE_BAD_FUNCTION_ARGUMENT; else if((x & 0xFE000000) || (beg == end)) return CURLE_BAD_FUNCTION_ARGUMENT; - y = *(const unsigned char *)beg++; + y = *beg++; x = (x << 7) | (y & 0x7F); } while(y & 0x80); result = curlx_dyn_addf(store, ".%u", x); @@ -469,7 +471,7 @@ UNITTEST CURLcode encodeOID(struct dynbuf *store, */ static CURLcode OID2str(struct dynbuf *store, - const char *beg, const char *end) + const uint8_t *beg, const uint8_t *end) { CURLcode result = CURLE_OK; if(beg < end) { @@ -651,10 +653,12 @@ UNITTEST CURLcode ASN1tostr(struct dynbuf *store, result = OID2str(store, elem->beg, elem->end); break; case CURL_ASN1_UTC_TIME: - result = UTime2str(store, elem->beg, elem->end); + result = UTime2str(store, (const char *)elem->beg, + (const char *)elem->end); break; case CURL_ASN1_GENERALIZED_TIME: - result = GTime2str(store, elem->beg, elem->end); + result = GTime2str(store, (const char *)elem->beg, + (const char *)elem->end); break; case CURL_ASN1_UTF8_STRING: case CURL_ASN1_NUMERIC_STRING: @@ -682,10 +686,10 @@ static CURLcode encodeDN(struct dynbuf *store, struct Curl_asn1Element *dn) struct Curl_asn1Element atv; struct Curl_asn1Element oid; struct Curl_asn1Element value; - const char *p1; - const char *p2; - const char *p3; - const char *str; + const uint8_t *p1; + const uint8_t *p2; + const uint8_t *p3; + const uint8_t *str; CURLcode result = CURLE_OK; bool added = FALSE; struct dynbuf temp; @@ -717,7 +721,7 @@ static CURLcode encodeDN(struct dynbuf *store, struct Curl_asn1Element *dn) if(result) goto error; - str = curlx_dyn_ptr(&temp); + str = curlx_dyn_uptr(&temp); if(!str) { result = CURLE_BAD_FUNCTION_ARGUMENT; @@ -738,7 +742,7 @@ static CURLcode encodeDN(struct dynbuf *store, struct Curl_asn1Element *dn) } /* Encode attribute name. */ - result = curlx_dyn_add(store, str); + result = curlx_dyn_add(store, (const char *)str); if(result) goto error; @@ -770,13 +774,14 @@ static CURLcode encodeDN(struct dynbuf *store, struct Curl_asn1Element *dn) * See RFC 5280. */ int Curl_parseX509(struct Curl_X509certificate *cert, - const char *beg, const char *end) + const uint8_t *beg, const uint8_t *end) { struct Curl_asn1Element elem; struct Curl_asn1Element tbsCertificate; - const char *ccp; - static const char defaultVersion = 0; /* v1. */ + const uint8_t *ccp; + static const uint8_t defaultVersion = 0; /* v1. */ + memset(cert, 0, sizeof(*cert)); cert->certificate.header = NULL; cert->certificate.beg = beg; cert->certificate.end = end; @@ -832,6 +837,9 @@ int Curl_parseX509(struct Curl_X509certificate *cert, return -1; if(!getASN1Element(&cert->notAfter, ccp, elem.end)) return -1; + /* These two fields MUST consume all of the validity elem */ + if(elem.end != cert->notAfter.end) + return -1; /* Get subject. */ beg = getASN1Element(&cert->subject, beg, end); if(!beg) @@ -852,10 +860,10 @@ int Curl_parseX509(struct Curl_X509certificate *cert, cert->issuerUniqueID.tag = cert->subjectUniqueID.tag = 0; cert->extensions.tag = elem.tag = 0; cert->issuerUniqueID.header = cert->subjectUniqueID.header = NULL; - cert->issuerUniqueID.beg = cert->issuerUniqueID.end = ""; - cert->subjectUniqueID.beg = cert->subjectUniqueID.end = ""; + cert->issuerUniqueID.beg = cert->issuerUniqueID.end = beg; + cert->subjectUniqueID.beg = cert->subjectUniqueID.end = beg; cert->extensions.header = NULL; - cert->extensions.beg = cert->extensions.end = ""; + cert->extensions.beg = cert->extensions.end = beg; if(beg < end) { beg = getASN1Element(&elem, beg, end); if(!beg) @@ -889,7 +897,7 @@ int Curl_parseX509(struct Curl_X509certificate *cert, static CURLcode dumpAlgo(struct dynbuf *store, struct Curl_asn1Element *param, - const char *beg, const char *end) + const uint8_t *beg, const uint8_t *end) { struct Curl_asn1Element oid; @@ -902,7 +910,7 @@ static CURLcode dumpAlgo(struct dynbuf *store, param->tag = 0; param->beg = param->end = end; if(beg < end) { - const char *p = getASN1Element(param, beg, end); + const uint8_t *p = getASN1Element(param, beg, end); if(!p) return CURLE_BAD_FUNCTION_ARGUMENT; } @@ -973,7 +981,7 @@ static int do_pubkey(struct Curl_easy *data, int certnum, const char *algo, { struct Curl_asn1Element elem; struct Curl_asn1Element pk; - const char *p; + const uint8_t *p; /* Generate all information records for the public key. */ @@ -1005,7 +1013,7 @@ static int do_pubkey(struct Curl_easy *data, int certnum, const char *algo, return 1; if(curl_strequal(algo, "rsaEncryption")) { - const char *q; + const uint8_t *q; size_t len; p = getASN1Element(&elem, pk.beg, pk.end); @@ -1018,7 +1026,7 @@ static int do_pubkey(struct Curl_easy *data, int certnum, const char *algo, len = ((elem.end - q) * 8); if(len) { unsigned int i; - for(i = *(const unsigned char *)q; !(i & 0x80); i <<= 1) + for(i = *q; !(i & 0x80); i <<= 1) len--; } if(len > 32) @@ -1084,8 +1092,8 @@ static CURLcode DNtostr(struct dynbuf *store, struct Curl_asn1Element *dn) CURLcode Curl_extract_certinfo(struct Curl_easy *data, int certnum, - const char *beg, - const char *end) + const uint8_t *beg, + const uint8_t *end) { struct Curl_X509certificate cert; struct Curl_asn1Element param; @@ -1094,7 +1102,7 @@ CURLcode Curl_extract_certinfo(struct Curl_easy *data, struct dynbuf out; CURLcode result = CURLE_OK; unsigned int version; - const char *ptr; + const uint8_t *ptr; int rc; if(!data->set.ssl.certinfo) @@ -1133,7 +1141,7 @@ CURLcode Curl_extract_certinfo(struct Curl_easy *data, /* Version (always fits in less than 32 bits). */ version = 0; for(ptr = cert.version.beg; ptr < cert.version.end; ptr++) - version = (version << 8) | *(const unsigned char *)ptr; + version = (version << 8) | *ptr; if(data->set.ssl.certinfo) { result = curlx_dyn_addf(&out, "%x", version); if(result) diff --git a/lib/vtls/x509asn1.h b/lib/vtls/x509asn1.h index dda55ac77fcc..cf97fffba63f 100644 --- a/lib/vtls/x509asn1.h +++ b/lib/vtls/x509asn1.h @@ -77,11 +77,11 @@ struct Curl_asn1Element; /* ASN.1 parsed element. */ struct Curl_asn1Element { - const char *header; /* Pointer to header byte. */ - const char *beg; /* Pointer to element data. */ - const char *end; /* Pointer to 1st byte after element. */ - unsigned char eclass; /* ASN.1 element class. */ - unsigned char tag; /* ASN.1 element tag. */ + const uint8_t *header; /* Pointer to header byte. */ + const uint8_t *beg; /* Pointer to element data. */ + const uint8_t *end; /* Pointer to 1st byte after element. */ + uint8_t eclass; /* ASN.1 element class. */ + uint8_t tag; /* ASN.1 element tag. */ BIT(constructed); /* Element is constructed. */ }; @@ -109,11 +109,11 @@ struct Curl_X509certificate { */ int Curl_parseX509(struct Curl_X509certificate *cert, - const char *beg, const char *end); + const uint8_t *beg, const uint8_t *end); CURLcode Curl_extract_certinfo(struct Curl_easy *data, int certnum, - const char *beg, const char *end); + const uint8_t *beg, const uint8_t *end); CURLcode Curl_verifyhost(struct Curl_cfilter *cf, struct Curl_easy *data, - const char *beg, const char *end); + const uint8_t *beg, const uint8_t *end); #endif /* USE_GNUTLS || USE_WOLFSSL || USE_SCHANNEL || USE_MBEDTLS || USE_RUSTLS */ #endif /* HEADER_CURL_X509ASN1_H */ diff --git a/tests/unit/unit1651.c b/tests/unit/unit1651.c index fc84b3365768..a91b238deb96 100644 --- a/tests/unit/unit1651.c +++ b/tests/unit/unit1651.c @@ -32,7 +32,7 @@ static CURLcode test_unit1651(const char *arg) /* cert captured from gdb when connecting to curl.se on October 26 2018. */ - static unsigned char cert[] = { + static uint8_t cert[] = { 0x30, 0x82, 0x0F, 0x5B, 0x30, 0x82, 0x0E, 0x43, 0xA0, 0x03, 0x02, 0x01, 0x02, 0x02, 0x0C, 0x08, 0x77, 0x99, 0x2C, 0x6B, 0x67, 0xE1, 0x18, 0xD6, 0x66, 0x66, 0x9E, 0x30, 0x0D, 0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, @@ -364,8 +364,8 @@ static CURLcode test_unit1651(const char *arg) }; CURLcode result; - const char *beg = (const char *)&cert[0]; - const char *end = (const char *)&cert[sizeof(cert)]; + const uint8_t *beg = &cert[0]; + const uint8_t *end = &cert[sizeof(cert)]; struct Curl_easy *data; int i; int byte; diff --git a/tests/unit/unit1657.c b/tests/unit/unit1657.c index 79f026bebad6..0413e3317346 100644 --- a/tests/unit/unit1657.c +++ b/tests/unit/unit1657.c @@ -23,7 +23,8 @@ ***************************************************************************/ #include "unitcheck.h" -#if defined(USE_GNUTLS) || defined(USE_SCHANNEL) || defined(USE_MBEDTLS) +#if defined(USE_GNUTLS) || defined(USE_WOLFSSL) || defined(USE_SCHANNEL) || \ + defined(USE_MBEDTLS) || defined(USE_RUSTLS) #include "vtls/x509asn1.h" struct test1657_spec { @@ -37,8 +38,8 @@ static CURLcode make1657_nested(const struct test1657_spec *spec, { CURLcode result; size_t i; - unsigned char open_undef[] = { 0x32, 0x80 }; - unsigned char close_undef[] = { 0x00, 0x00 }; + uint8_t open_undef[] = { 0x32, 0x80 }; + uint8_t close_undef[] = { 0x00, 0x00 }; for(i = 0; i < spec->n; ++i) { result = curlx_dyn_addn(buf, open_undef, sizeof(open_undef)); @@ -65,8 +66,8 @@ static bool do_test1657(const struct test1657_spec *spec, size_t i, { CURLcode result; struct Curl_asn1Element elem; - const char *in; - const char *ptr; + const uint8_t *in; + const uint8_t *ptr; memset(&elem, 0, sizeof(elem)); curlx_dyn_reset(buf); @@ -75,7 +76,7 @@ static bool do_test1657(const struct test1657_spec *spec, size_t i, curl_mfprintf(stderr, "test %zu: error setting buf %d\n", i, (int)result); return FALSE; } - in = curlx_dyn_ptr(buf); + in = curlx_dyn_uptr(buf); ptr = getASN1Element(&elem, in, in + curlx_dyn_len(buf)); result = ptr ? CURLE_OK : CURLE_BAD_FUNCTION_ARGUMENT; if(result != spec->result_exp) { @@ -86,6 +87,59 @@ static bool do_test1657(const struct test1657_spec *spec, size_t i, return TRUE; } +struct test1657_cert_spec { + const char * const pem; + bool exp_success; +}; + +static const struct test1657_cert_spec test1657_certs[] = { + { /* a valid certificate */ + "MIIBGTCBzKADAgECAhQ8Ssn8BaVsqlKKccv81NFuKY1vATAFBgMrZXAwADAeFw0y" + "NjA5MDIwNDU0MjZaFw0yNjEwMDIwNDU0MjZaMAAwKjAFBgMrZXADIQBeG0S343s9" + "5yRmIK8xZmGvUmiiYvRH5ZMcPq6rvHHKwqNYMFYwNQYDKgMEBC4wADAqMAUGAytl" + "cAMhAPP5TXSe2WHt/bebbP8eHPXrG3LNxYz8TnHNgpwTFsT7MB0GA1UdDgQWBBR6" + "aqL9LrZiIpgYCy0X4fNrhgFQsTAFBgMrZXADQQALU0OHGcIvfTwyin/+u9csJ9hZ" + "W0c+XdHJY5uDdCR1O92K4Vnozf/QCe7ITOJ8aUnCs7Jgf5xrDgyXZNdum04A", + TRUE + }, + { /* a certificate with "validity" element that has too long length */ + "MIIBGTCBzKADAgECAhQ8Ssn8BaVsqlKKccv81NFuKY1vATAFBgMrZXAwADBZFw0y" + "NjA5MDIwNDU0MjZaFw0yNjEwMDIwNDU0MjZaMAAwKjAFBgMrZXADIQBeG0S343s9" + "5yRmIK8xZmGvUmiiYvRH5ZMcPq6rvHHKwqNYMFYwNQYDKgMEBC4wADAqMAUGAytl" + "cAMhAPP5TXSe2WHt/bebbP8eHPXrG3LNxYz8TnHNgpwTFsT7MB0GA1UdDgQWBBR6" + "aqL9LrZiIpgYCy0X4fNrhgFQsTAFBgMrZXADQQALU0OHGcIvfTwyin/+u9csJ9hZ" + "W0c+XdHJY5uDdCR1O92K4Vnozf/QCe7ITOJ8aUnCs7Jgf5xrDgyXZNdum04A", + FALSE + } +}; + +static bool test1657_parse_cert(size_t i, + const struct test1657_cert_spec *spec) +{ + struct Curl_X509certificate cert; + CURLcode result; + uint8_t *cert_der; + size_t cert_der_len; + bool success; + + result = curlx_base64_decode(spec->pem, &cert_der, &cert_der_len); + if(result) { + curl_mfprintf(stderr, "cert %zu: base64 decoding failed: %d\n", + i, (int)result); + return FALSE; + } + + success = !Curl_parseX509(&cert, cert_der, cert_der + cert_der_len); + if(success != spec->exp_success) { + curl_mfprintf(stderr, "cert %zu: parsing DER %sfailed\n", i, + spec->exp_success ? "" : "should have "); + return FALSE; + } + + curlx_free(cert_der); + return TRUE; +} + static CURLcode test_unit1657(const char *arg) { UNITTEST_BEGIN_SIMPLE @@ -105,7 +159,14 @@ static CURLcode test_unit1657(const char *arg) if(!do_test1657(&test1657_specs[i], i, &dbuf)) all_ok = FALSE; } - fail_unless(all_ok, "some tests of getASN1Element() fails"); + fail_unless(all_ok, "some getASN1Element() tests failed"); + + all_ok = TRUE; + for(i = 0; i < CURL_ARRAYSIZE(test1657_certs); ++i) { + if(!test1657_parse_cert(i, &test1657_certs[i])) + all_ok = FALSE; + } + fail_unless(all_ok, "some certificate tests failed"); curlx_dyn_free(&dbuf); curl_global_cleanup(); diff --git a/tests/unit/unit1666.c b/tests/unit/unit1666.c index cf272da42fcc..28e53c09c872 100644 --- a/tests/unit/unit1666.c +++ b/tests/unit/unit1666.c @@ -29,20 +29,20 @@ #include "vtls/vtls.h" struct test_1666 { - const char *oid; + const uint8_t *oid; const size_t size; const char *dotted; CURLcode result_exp; }; /* the size of the object needs to deduct the null-terminator */ -#define OID(x) STRCONST(x) +#define OID(x) (const uint8_t *)STRCONST(x) static bool test1666(const struct test_1666 *spec, size_t i, struct dynbuf *dbuf) { CURLcode result; - const char *oid = spec->oid; + const uint8_t *oid = spec->oid; bool ok = TRUE; curlx_dyn_reset(dbuf); @@ -71,8 +71,8 @@ static CURLcode test_unit1666(const char *arg) UNITTEST_BEGIN_SIMPLE static const struct test_1666 test_specs[] = { - { "", 0, "", CURLE_BAD_FUNCTION_ARGUMENT }, - { "\x81", 0, "", CURLE_BAD_FUNCTION_ARGUMENT }, + { OID(""), "", CURLE_BAD_FUNCTION_ARGUMENT }, + { (const uint8_t *)"\x81", 0, "", CURLE_BAD_FUNCTION_ARGUMENT }, { OID("\x8F\xFF\xFF\xFF\x7F"), "2.4294967215", CURLE_OK }, { OID("\x90\x80\x80\x80\x00"), "", CURLE_BAD_FUNCTION_ARGUMENT }, { OID("\x88\x80\x80\x80\x4F"), "2.2147483647", CURLE_OK }, diff --git a/tests/unit/unit1667.c b/tests/unit/unit1667.c index 5fedfd2b40af..5e7325ce75b8 100644 --- a/tests/unit/unit1667.c +++ b/tests/unit/unit1667.c @@ -49,7 +49,7 @@ static bool test1667(const struct test_1667 *spec, size_t i, /* setup private struct for this invoke */ elem.tag = spec->tag; elem.header = NULL; - elem.beg = spec->asn1; + elem.beg = (const uint8_t *)spec->asn1; elem.end = elem.beg + spec->size; elem.eclass = 0; elem.constructed = 0; diff --git a/tests/unit/unit1676.c b/tests/unit/unit1676.c index 5f33f9f2850d..e1d2d92f65a3 100644 --- a/tests/unit/unit1676.c +++ b/tests/unit/unit1676.c @@ -44,7 +44,7 @@ static CURLcode test_unit1676(const char *arg) * * OID 1.2.840.10046.2.1 = dhpublicnumber */ - static const unsigned char cert[] = { + static const uint8_t cert[] = { 0x30, 0x81, 0x85, 0x30, 0x72, 0xA0, 0x03, 0x02, 0x01, 0x02, 0x02, 0x01, 0x01, 0x30, 0x0B, 0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x0B, 0x30, 0x0F, 0x31, 0x0D, 0x30, 0x0B, 0x06, 0x03, 0x55, 0x04, @@ -60,8 +60,8 @@ static CURLcode test_unit1676(const char *arg) }; CURLcode result; - const char *beg = (const char *)&cert[0]; - const char *end = (const char *)&cert[sizeof(cert)]; + const uint8_t *beg = &cert[0]; + const uint8_t *end = &cert[sizeof(cert)]; struct Curl_easy *data; struct curl_slist *slist; const char *dhp_value = NULL; From 3bfd12ac51b1a4c0f72ea5c9e92578e311d37990 Mon Sep 17 00:00:00 2001 From: Daniel Stenberg Date: Thu, 3 Sep 2026 08:06:16 +0200 Subject: [PATCH 013/481] RELEASE-NOTES: synced And bump curlver.h to 8.22.1 --- RELEASE-NOTES | 651 ++--------------------------------------- include/curl/curlver.h | 6 +- 2 files changed, 26 insertions(+), 631 deletions(-) diff --git a/RELEASE-NOTES b/RELEASE-NOTES index 8342fec4564d..456bbf07ec49 100644 --- a/RELEASE-NOTES +++ b/RELEASE-NOTES @@ -1,325 +1,25 @@ -curl and libcurl 8.22.0 +curl and libcurl 8.22.1 Public curl releases: 276 Command line options: 278 curl_easy_setopt() options: 312 Public functions in libcurl: 100 - Authors: 1518 - Contributors: 3786 + Authors: 1520 + Contributors: 3790 This release includes the following changes: - o gssapi: add support for Apple GSS Framework [72] - o hardening: add API guards [64] - o RFC 9421 HTTP Message Signatures support [108] - o spnego: block NTLM fallback in SPNEGO negotiation [151] - o TLS: drop support for TLS-SRP [71] - o vquic: add option to use Apple fast UDP [137] This release includes the following bugfixes: - o altsvc: continue after unknown parameters [198] - o asyn-thrdd: retry link-local ipv6 if missing scope id [118] - o autotools: minor fixes and improvements [33] - o build: always use local `inet_pton()`/`inet_ntop()` implementations [56] - o build: assume POSIX `select()` is available [166] - o build: clear `Require.private` for static-only builds in `libcurl.pc` [188] - o build: drop `dirent.h` and `opendir()` detections on Windows [186] - o build: drop detecting `gettimeofday()` on Windows [184] - o build: drop superfluous `STDC_HEADERS` macro [51] - o build: enable thread-safe `getaddrinfo()` for OpenBSD [35] - o build: minor debug option message fixes/improvements [200] - o build: require `!NDEBUG` for debug-enabled (aka development) builds [202] - o build: strip duplicate spaces after `Libs.private:` in `libcurl.pc` [191] - o build: strip trailing spaces from `libcurl.pc` [194] - o cd2nroff: fix backslashes for 4-space indent lines [104] - o cd2nroff: stricter checks for asterisks for italics [73] - o cf-ngtcp2-cmn: de-duplicate `ngtcp2_conn_client_new()` call code [156] - o cf-ngtcp2-cmn: initialize new callback ptr for ngtcp2 1.24.0+ [52] - o cf-socket: avoid broken NetBSD SOCK_NONBLOCK [275] - o cf-socket: disable TCP SYN retransmissions for localhost on Windows [164] - o cfilters: fix event-based connection shutdown [91] - o clock: save one call [286] - o cmake/FindLibgsasl: fix to set `LIBGSASL_VERSION` with pkg-config detection [229] - o cmake: check libgsasl version at configure time [277] - o cmake: dedupe expressions into local vars in `cmake_uninstall.in.cmake` [9] - o cmake: fix not to build `tunits` when `BUILD_CURL_EXE=OFF` [7] - o cmake: flatten build tree, tidy up base dir variables [12] - o cmake: minor improvements to `cmake_uninstall.in.cmake` [54] - o cmake: optimize OpenSSL fork detection [228] - o cmake: replace `remove` command with `rm` and pass arg safely [11] - o cmake: robustify base path in local file reference [15] - o cmake: stop probing unused `float.h` for `STDC_HEADERS` [10] - o cmake: use built-in variable and target property dump functions with CMake 4.5+ [155] - o config-riscos.h: delete handcrafted RISC OS config header, in favor of autotools [178] - o config-win32.h: drop UWP, c-ares, simplify more [231] - o config-win32.h: limit use to MSVC IDE Project builds [193] - o configure: clarify --enable-debug option [133] - o configure: fix misleading error messages [42] - o configure: link `-lcrypt32` instead of `-lm` for wolfSSL on Windows [79] - o configure: only check in the watt library if WATT_ROOT is set [120] - o configure: remove double check for GnuTLS [21] - o configure: set ldap lib to no by default for non-finds [18] - o conncache: apply multi limits to transfers using a shared pool [41] - o conncache: conn upkeep/alive: move and enhance [152] - o conncache: connection alive checks intervals [20] - o conncache: don't assume curl_off_t increment wrap-around [138] - o conncache: guess maxconnects different [289] - o connect: connection close tweaks [112] - o connect: only set connect timer on first socket [206] - o connection reuse: age check [261] - o connection reuse: check SSL configs when doing a scheme upgrade [249] - o connections: use admin handles only for maintenance [213] - o content_encoding: exact-match the identity transfer-coding token [189] - o content_encoding: give a clear error on multi-member gzip [46] - o cookie: cookies set for an exact PSL domain is host-only [304] - o cookie: improve TAB handling [258] - o cookie: refuse to load cookies set against a PSL domain [139] - o CREDENTIALS.md: remove comment about empty user/pass [50] - o ctype: exclude control bytes from ISPRINT and ISGRAPH [119] - o curl: help category cleanups [169] - o curl_gssapi: document/update feature availability [145] - o curl_threads: always use native threads/mutex on Windows [185] - o curl_trc: remove unused expire timers [147] - o curl_url_set.md: expand the CURLU_NO_AUTHORITY description [134] - o curl_ws_meta.md: polish and better vocabulary [19] - o CURLOPT_HEADERFUNCTION.md: document folded header unfolding [53] - o CURLOPT_SOCKOPTFUNCTION.md: ALREADY_CONNECTED does not work for HTTP/3 [262] - o CURLOPT_SSH_*_KEYFILE: used for setting up, then no more [48] - o CURLOPT_UNRESTRICTED_AUTH.md: 'Authorization', not 'Authentication' [74] - o CURLOPT_USERNAME.md: ambient username caveats [271] - o CURLSHOPT_(UN)SHARE.md: do not modify shares while in use [44] - o curlx_inet_ntop: return `CURLcode`, drop setting `errno` [237] - o curlx_inet_pton: drop setting `errno` on error [236] - o DEPRECATE.md: HTTP/2 Server Push gets removed in March 2027 [174] - o dict: avoid busy-loop in sendf() when the socket is not writable [99] - o dist: fix to drop test bundle .c files from the source tarball [305] - o dnsd: fix bounds check in `read_https_alpn_part()` [143] - o docs/INTERNALS.md -> docs/DEPENDENCIES.md [127] - o docs: clarify that cookies need domain set to match [224] - o docs: connection reuse behavior for socket callbacks [219] - o docs: make 5 example snippets compile cleanly with clang [192] - o docs: mention possible auth option conflicts [114] - o docs: remove doubled word in SECURITY-ADVISORY.md [183] - o DoH: improvements [203] - o easy: fix unused global on non-Windows [292] - o easy_lock: silence `portability-no-assembler` with clang-tidy 23.1.0+ [291] - o FAQ: correct an option typo [278] - o file: support directory listing on Windows [205] - o filter: change time reporting [235] - o FTP: fix TLS session reuse on the data connection [80] - o ftp: reject control bytes in ACCT and alternative-to-user [26] - o gitignore: maintenance updates [170] - o gopher: fix partial sends of CRLF [288] - o gopher: reject CR and LF in the selector [1] - o h2 push: use squeaky clean easy handle [246] - o h2: bootstrap max streams from multi handle if in use [132] - o h3-proxy: fix NULL deref when non-:status header arrives before :status [167] - o Happy Eyeballing v3: resolution delay of 25ms [232] - o header api: add guards [168] - o headers: name the arguments the way the definitions name them [234] - o HISTORY.md: PSL support in 2015 - o HISTORY: add when c-ares support was introduced (2004) - o HISTORY: September 1999: started using CVS - o hostip: only cache negative resolves for authoritative answers [16] - o hsts: only match the exact strings [269] - o http digest: tie peer/credentials on input [264] - o http2: make server push transfers inherit share from parent [81] - o http2: remove assert in ingress processing [272] - o http: avoid length underflow in Curl_compareheader [78] - o http: custom Authorization: header overrides Negotiate [223] - o http: fix non-tunneling proxy hostname use [116] - o http: stop dropping large custom headers [69] - o http: trim custom header name before the Authorization drop [17] - o httpsrr: DoH with HTTPS, fix response handling [113] - o idn: restore `MultiByteToWideChar()` `MB_ERR_INVALID_CHARS` flag [103] - o imap: APPEND CRLF fix [256] - o include: include when building for modern Linux. [308] - o INSTALL.md: add building-from-source overview section [29] - o INTERNALS.md: require quiche 0.20.0+ [101] - o ipv6 scope_id: set from first peer [242] - o keylog: add a random size argument to Curl_tls_keylog_write() [180] - o ldap: base64-encode LDIF values beginning with colon or less-than [218] - o ldap: reject control characters in URL-decoded filter values [196] - o ldap: support empty username and password [106] - o ldap: support insecure mode for Windows native LDAP [3] - o lib1587: fix gcc `-Wconversion` with LibreSSL on Windows, test in CI [6] - o lib2405: adjust for non-threaded builds [149] - o lib: add "Curl_" prefix to two global functions [84] - o lib: add multi_wakeup_internal [86] - o lib: drop unused `system_win32.h` includes [290] - o lib: fix 'ns' -> 'us' in trace messages [57] - o lib: new easy option string storage [215] - o lib: optimize struct layouts for reduced memory usage [212] - o lib: ratelimit timestamps [14] - o lib: silence gcc-16 compiler warnings `-Wmaybe-uninitialized` [243] - o lib: update mentions of the legacy "sessionhandle" [157] - o libcurl.md: emphasize that the output needs checking [259] - o libcurl.pc: add `License` tag [190] - o libcurl.pc: add Copyright tag to the pkgconf file - o libcurl.pc: add the Link.ABI and Source tags [210] - o macos sectrust: fail ocsp verify when not builtin [252] - o Makefile.am: improve etags [257] - o mbedtls: enforce verifyhost when verifypeer is disabled [208] - o mbedtls: replace `memset()` with `psa_hash_operation_init()` [28] - o md5: replace magic numbers with `MD5_DIGEST_LEN` [122] - o mime.c: avoid integer overflow in base64 size calculation [105] - o mime: reject CR and LF in mail part name and filename [30] - o mod_curltest: fix compiler warnings [49] - o mprintf: acknowledge %F [245] - o mprintf: avoid never-ending loop for positive-infinite [247] - o mprintf: fix long double output [250] - o mqtt: reject control bytes in the topic [43] - o multi: cap expire times to INT_MAX internally [216] - o multi: forbid curl_easy_pause from within multi socket callback [22] - o multi: hold timeout values in 'int' instead of 'long' [165] - o multi: remove #if 0'ed code that uses old struct [150] - o multi: shrink expire timer indices [199] - o multi: timeout improvements [209] - o multi: use index list for expire timeouts [197] - o multi: xfer table initial size and growth [255] - o multihandle: move two struct fields [163] - o ngtcp2+openssL: fix early data [225] - o ngtcp2: avoid NULL deref in cf_ngtcp2_send [260] - o ngtcp2: clean up after ngtcp2 in `curl_global_cleanup` [126] - o ngtcp2: let verify failures win over expiry processing errors [98] - o openldap: handle Curl_sasl_continue() returns better [45] - o openssl+sectrust: fix session reuse [4] - o openssl+sectrust: move session verified set into result check [82] - o openssl: avoid conn reuse if provider is used [214] - o openssl: avoid strlen() on the data from OpenSSL [280] - o openssl: aws-lc ocsp workaround [263] - o openssl: drop unused pre-OpenSSL3 `ctx_option_t` typedef [8] - o openssl: fix DER buffer leak in Apple SecTrust verification [217] - o openssl: no server cert is only okay if also not pinned [226] - o openssl: prefer modern API flavors for `EVP_MD_CTX` new/free [47] - o openssl: replace stray legacy API variant with `EVP_DigestInit_ex()` [27] - o os400: port latest header files changes to ILE/RPG interface [241] - o os400: rewrite upper ebcdic wrappers using dynbuf [227] - o progress: cleanup, less memory [179] - o protocol: simpler Curl_getn_scheme runs faster [239] - o proxy: CONNECT trailers handling [251] - o psl: update a comment to understandable English [162] - o pytest: update two H3 tests for nghttp3 1.18.0+ [158] - o quic: upload improvements [276] - o quiche: set the max field section size [100] - o rtsp: refactor method handling and improve error checks [161] - o runtests: allow comments in `setenv` section, merge sections in test433 [89] - o runtests: fix `mode="warn"` tests passing unconditionally, fix test 1752 [66] - o runtests: flush cached test parts when (re)loading a file [95] - o runtests: restore `-k` option and actively process as no-op [32] - o sasl: fix zero-length response encoding [36] - o schannel: add ALPN support for mingw-w64 <9 and =128-bit pointers [107] - o url: fix handling of empty user in NTLM matching [221] - o url: fix negotiate/ntlm connection reuse [176] - o url: reject control codes in credentials set via CURLOPT [70] - o urlapi: allow URLs to not have userauth (hostname) [92] - o urlapi: avoid dedotdotify() if possible [182] - o urlapi: clear password buffer on error path [121] - o urlapi: do not keep an internal port string [31] - o urlapi: improved return codes [148] - o urlapi: preserve empty markers in relative URLs [61] - o urldata: cleanups [175] - o urldata: drop four strings from the aptr struct [136] - o urldata: sort the connectdata struct fields by size [177] - o VERSIONS.md: document Rock-solid curl releases [201] - o vms: fix symbol typo and missing closing quotes in `config_h.com` [124] - o vquic: add Curl_ prefix to some global functions [76] - o vquic: initialize new callback slot for nghttp3 v1.18.0+ [87] - o vquic: silence `-Wmissing-field-initializers` for nghttp3/ngtcp2 callback tables [159] - o vquic: use ngtcp2 v1.25.0 new close2 callback [154] - o vssh: keyfile use cleanups [83] - o vssh: silence gcc-11 `-Wnull-dereference`, dedupe `CURL_EASY_STR()` calls [240] - o vtls: move 'native_ca_store' ssl_config_data => ssl_primary_config [211] - o vtls_scache: use case sensitive path match - o VULN-DISCLOSURE-POLICY.md: issues that should be found by tests are LOW [5] - o wcurl: import v2026.08.30 [279] - o websocket: pause writing and meta data fix [135] - o winsock: drop redundant version checks at initialization [284] - o wolfssl: do not run Curl_wssl_setup_x509_store() twice [265] - o wolfssl: fix build for wolfssl without bio chain support [75] - o ws: fix write callback error handling [204] - o ws: pause/unpause write handling [55] + o CURLOPT_SHARE.md: remove bad explanation for cookie sharing [7] + o docs: add the missing .html to the url-encode book link [3] + o doh: reject undersized HTTPS RDATA [11] + o ngtcp2: shrink max sendbuf [13] + o test1399: improve reliability [9] + o vquic: fix preprocessor check for old Darwin versions [8] + o vtls: fix Curl_cert_hostcheck when the pattern is not a C string [12] + o x509: stricter parsing [2] This release includes the following known bugs: @@ -341,323 +41,18 @@ Planned upcoming removals include: This release would not have looked like this without help, code, reports and advice from friends like these: - 11soda11, 1rhino2 on hackerone, accl on hackerone, AlanKingPL, Alb3e3, - Alexey Samsonov, Alhuda Khan, Anton Karpov, anupamme, Arham Wani, - Athos Ribeiro, Bartel Sielski, Bigtang on hackerone, Bill Mill, - Bryan Henderson, Carlos Henrique Lima Melara, CatboxParadox, - Christian Ullrich, Christoph Reiter, claudex on github, Collin Funk, - cybertron10 on github, Dan Fandrich, Daniel Gustafsson, Daniel Stenberg, - dependabot[bot], ed0d2b2ce19451f2 on github, Emmanuel Ugwu, Eunsoo Kim, - felix h, firexinghe on github, Fred Klassen, GLaDOS-418 on github, - Graham Campbell, Hendrik Hübner, HwangRock, Igor Morgenstern, itzTanos29, - Jace Laquerre, Jayanth Vennamreddy, Joel Depooter, Johannes Schindelin, - John Verne, Keng-Yu Lin, Kieran Gannon, kit-ty-kate on github, - Laurent Sabourin, Manuel Sánchez-Guijarro, Marcel Jamin, marco-jardim, - Martin Dukek, martin-fzi on github, Matthew John Cheetham, Max Dymond, - Memduh Çelik, Patrick Monnerat, Pavel Sobolev, pszemus on github, - Ralf Mueller, Ramesh Adhikari, Rarylson Freitas, Ray Satiro, renovate[bot], - Rito Rhymes, RMMoreton on github, Roger Leigh, Ron Kuper, Ross Burton, - Sameeh Jubran, Sam James, Samuel Dainard, Samuel Henrique, Scott Talbert, - sdgh179 on github, Sergei Zimmerman, smaeljaish on hackerone, Stanislav Fort, - Stefan Eissing, Stephan Zeisberg, stze on hackerone, Thomas Chauchefoin, - Viktor Szakats, xmoezzz on github, Yoshiro Yoneya, zaveshaa - (85 contributors) + Artem Prilutskiy, Christian Hesse, Daniel Lang, Daniel Stenberg, + David Benjamin, Max Dymond, Ray Satiro, Roshan Ramani, Stefan Eissing, + Un1q32 + (10 contributors) References to bug reports and discussions on issues: - [1] = https://curl.se/bug/?i=22116 - [2] = https://curl.se/bug/?i=22158 - [3] = https://curl.se/bug/?i=22152 - [4] = https://curl.se/bug/?i=22235 - [5] = https://curl.se/bug/?i=22190 - [6] = https://curl.se/bug/?i=22195 - [7] = https://curl.se/bug/?i=22198 - [8] = https://curl.se/bug/?i=22197 - [9] = https://curl.se/bug/?i=22194 - [10] = https://curl.se/bug/?i=22191 - [11] = https://curl.se/bug/?i=22193 - [12] = https://curl.se/bug/?i=22192 - [13] = https://curl.se/bug/?i=22344 - [14] = https://curl.se/bug/?i=22292 - [15] = https://curl.se/bug/?i=22187 - [16] = https://curl.se/bug/?i=22302 - [17] = https://curl.se/bug/?i=22178 - [18] = https://curl.se/bug/?i=22308 - [19] = https://curl.se/bug/?i=22233 - [20] = https://curl.se/bug/?i=22169 - [21] = https://curl.se/bug/?i=22307 - [22] = https://curl.se/bug/?i=22179 - [23] = https://curl.se/bug/?i=22182 - [24] = https://curl.se/bug/?i=22232 - [25] = https://curl.se/bug/?i=22231 - [26] = https://curl.se/bug/?i=22301 - [27] = https://curl.se/bug/?i=22222 - [28] = https://curl.se/bug/?i=22220 - [29] = https://curl.se/bug/?i=22113 - [30] = https://curl.se/bug/?i=22247 - [31] = https://curl.se/bug/?i=22167 - [32] = https://curl.se/bug/?i=22100 - [33] = https://curl.se/bug/?i=22154 - [34] = https://curl.se/bug/?i=22150 - [35] = https://curl.se/bug/?i=22148 - [36] = https://curl.se/bug/?i=22218 - [37] = https://curl.se/bug/?i=22119 - [38] = https://curl.se/bug/?i=22165 - [39] = https://curl.se/bug/?i=22164 - [40] = https://curl.se/bug/?i=22136 - [41] = https://curl.se/bug/?i=22265 - [42] = https://curl.se/bug/?i=22300 - [43] = https://curl.se/bug/?i=22112 - [44] = https://curl.se/bug/?i=22217 - [45] = https://curl.se/bug/?i=22213 - [46] = https://curl.se/bug/?i=22156 - [47] = https://curl.se/bug/?i=22219 - [48] = https://curl.se/bug/?i=22211 - [49] = https://curl.se/bug/?i=22214 - [50] = https://curl.se/bug/?i=22212 - [51] = https://curl.se/bug/?i=22206 - [52] = https://curl.se/bug/?i=22205 - [53] = https://curl.se/bug/?i=22296 - [54] = https://curl.se/bug/?i=22201 - [55] = https://curl.se/bug/?i=22273 - [56] = https://curl.se/bug/?i=22170 - [57] = https://curl.se/bug/?i=22200 - [58] = https://curl.se/bug/?i=22280 - [59] = https://curl.se/bug/?i=22277 - [60] = https://curl.se/bug/?i=22294 - [61] = https://curl.se/bug/?i=22298 - [62] = https://curl.se/bug/?i=22303 - [63] = https://curl.se/bug/?i=22286 - [64] = https://curl.se/bug/?i=22237 - [65] = https://curl.se/bug/?i=22487 - [66] = https://curl.se/bug/?i=22388 - [67] = https://curl.se/bug/?i=22340 - [68] = https://curl.se/bug/?i=22253 - [69] = https://curl.se/bug/?i=22336 - [70] = https://curl.se/bug/?i=22236 - [71] = https://curl.se/bug/?i=21965 - [72] = https://curl.se/bug/?i=22052 - [73] = https://curl.se/bug/?i=22257 - [74] = https://curl.se/bug/?i=22256 - [75] = https://curl.se/bug/?i=22255 - [76] = https://curl.se/bug/?i=22254 - [77] = https://curl.se/bug/?i=22250 - [78] = https://curl.se/bug/?i=22338 - [79] = https://curl.se/bug/?i=22251 - [80] = https://curl.se/bug/?i=22225 - [81] = https://curl.se/bug/?i=22488 - [82] = https://curl.se/bug/?i=22248 - [83] = https://curl.se/bug/?i=22243 - [84] = https://curl.se/bug/?i=22245 - [85] = https://curl.se/bug/?i=22431 - [86] = https://curl.se/bug/?i=22272 - [87] = https://curl.se/bug/?i=22399 - [88] = https://curl.se/bug/?i=22210 - [89] = https://curl.se/bug/?i=22389 - [90] = https://curl.se/bug/?i=22346 - [91] = https://curl.se/bug/?i=22282 - [92] = https://curl.se/bug/?i=22279 - [93] = https://curl.se/bug/?i=22316 - [94] = https://curl.se/bug/?i=22433 - [95] = https://curl.se/bug/?i=22319 - [96] = https://curl.se/bug/?i=22323 - [97] = https://curl.se/bug/?i=22318 - [98] = https://curl.se/bug/?i=22317 - [99] = https://curl.se/bug/?i=22576 - [100] = https://curl.se/bug/?i=22331 - [101] = https://curl.se/bug/?i=22333 - [102] = https://curl.se/bug/?i=22334 - [103] = https://curl.se/bug/?i=22326 - [104] = https://curl.se/bug/?i=22393 - [105] = https://curl.se/bug/?i=22320 - [106] = https://curl.se/bug/?i=22162 - [107] = https://curl.se/bug/?i=22299 - [108] = https://curl.se/bug/?i=22386 - [109] = https://curl.se/bug/?i=22312 - [110] = https://curl.se/bug/?i=22374 - [111] = https://curl.se/bug/?i=22387 - [112] = https://curl.se/bug/?i=22379 - [113] = https://curl.se/bug/?i=22372 - [114] = https://curl.se/bug/?i=22648 - [115] = https://curl.se/bug/?i=22276 - [116] = https://curl.se/bug/?i=22382 - [117] = https://curl.se/bug/?i=22305 - [118] = https://curl.se/bug/?i=22330 - [119] = https://curl.se/bug/?i=22371 - [120] = https://curl.se/bug/?i=22380 - [121] = https://curl.se/bug/?i=21637 - [122] = https://curl.se/bug/?i=22377 - [123] = https://curl.se/bug/?i=22376 - [124] = https://curl.se/bug/?i=22375 - [125] = https://curl.se/bug/?i=21637 - [126] = https://curl.se/bug/?i=22363 - [127] = https://curl.se/bug/?i=22430 - [128] = https://curl.se/bug/?i=22369 - [129] = https://curl.se/bug/?i=22354 - [130] = https://curl.se/bug/?i=21467 - [131] = https://curl.se/bug/?i=22037 - [132] = https://curl.se/bug/?i=22418 - [133] = https://curl.se/bug/?i=22512 - [134] = https://curl.se/bug/?i=22515 - [135] = https://curl.se/bug/?i=22413 - [136] = https://curl.se/bug/?i=22603 - [137] = https://curl.se/bug/?i=22341 - [138] = https://curl.se/bug/?i=22569 - [139] = https://curl.se/bug/?i=22500 - [140] = https://curl.se/bug/?i=22568 - [141] = https://curl.se/bug/?i=22422 - [142] = https://curl.se/bug/?i=22421 - [143] = https://curl.se/bug/?i=22420 - [144] = https://curl.se/bug/?i=22415 - [145] = https://curl.se/bug/?i=22419 - [146] = https://curl.se/bug/?i=22471 - [147] = https://curl.se/bug/?i=22468 - [148] = https://curl.se/bug/?i=22337 - [149] = https://curl.se/bug/?i=22414 - [150] = https://curl.se/bug/?i=22467 - [151] = https://curl.se/bug/?i=21315 - [152] = https://curl.se/bug/?i=21806 - [153] = https://curl.se/bug/?i=22409 - [154] = https://curl.se/bug/?i=22270 - [155] = https://curl.se/bug/?i=22566 - [156] = https://curl.se/bug/?i=22401 - [157] = https://curl.se/bug/?i=22463 - [158] = https://curl.se/bug/?i=22397 - [159] = https://curl.se/bug/?i=22400 - [160] = https://curl.se/bug/?i=22460 - [161] = https://curl.se/bug/?i=22505 - [162] = https://curl.se/bug/?i=22502 - [163] = https://curl.se/bug/?i=22565 - [164] = https://curl.se/bug/?i=22494 - [165] = https://curl.se/bug/?i=22564 - [166] = https://curl.se/bug/?i=22448 - [167] = https://curl.se/bug/?i=22449 - [168] = https://curl.se/bug/?i=22530 - [169] = https://curl.se/bug/?i=22447 - [170] = https://curl.se/bug/?i=22445 - [171] = https://curl.se/bug/?i=22444 - [172] = https://curl.se/bug/?i=22443 - [173] = https://curl.se/bug/?i=22647 - [174] = https://curl.se/bug/?i=22490 - [175] = https://curl.se/bug/?i=22604 - [176] = https://curl.se/bug/?i=22528 - [177] = https://curl.se/bug/?i=22678 - [178] = https://curl.se/bug/?i=22598 - [179] = https://curl.se/bug/?i=22547 - [180] = https://curl.se/bug/?i=22560 - [181] = https://curl.se/bug/?i=22555 - [182] = https://curl.se/bug/?i=22557 - [183] = https://curl.se/bug/?i=22642 - [184] = https://curl.se/bug/?i=22594 - [185] = https://curl.se/bug/?i=22593 - [186] = https://curl.se/bug/?i=22588 - [187] = https://curl.se/bug/?i=22552 - [188] = https://curl.se/bug/?i=22548 - [189] = https://curl.se/bug/?i=22541 - [190] = https://curl.se/bug/?i=22545 - [191] = https://curl.se/bug/?i=22544 - [192] = https://curl.se/bug/?i=22638 - [193] = https://curl.se/bug/?i=22595 - [194] = https://curl.se/bug/?i=22536 - [195] = https://curl.se/bug/?i=22540 - [196] = https://curl.se/bug/?i=22524 - [197] = https://curl.se/bug/?i=22473 - [198] = https://curl.se/bug/?i=22644 - [199] = https://curl.se/bug/?i=22591 - [200] = https://curl.se/bug/?i=22532 - [201] = https://curl.se/bug/?i=22636 - [202] = https://curl.se/bug/?i=22484 - [203] = https://curl.se/bug/?i=22514 - [204] = https://curl.se/bug/?i=22627 - [205] = https://curl.se/bug/?i=22577 - [206] = https://curl.se/bug/?i=22587 - [207] = https://curl.se/bug/?i=22672 - [208] = https://curl.se/bug/?i=22475 - [209] = https://curl.se/bug/?i=22584 - [210] = https://curl.se/bug/?i=22519 - [211] = https://curl.se/bug/?i=22668 - [212] = https://curl.se/bug/?i=22585 - [213] = https://curl.se/bug/?i=22567 - [214] = https://curl.se/bug/?i=22665 - [215] = https://curl.se/bug/?i=22628 - [216] = https://curl.se/bug/?i=22579 - [217] = https://curl.se/bug/?i=22631 - [218] = https://curl.se/bug/?i=22339 - [219] = https://curl.se/bug/?i=20521 - [220] = https://curl.se/bug/?i=22623 - [221] = https://curl.se/bug/?i=22629 - [222] = https://curl.se/bug/?i=22721 - [223] = https://curl.se/bug/?i=22610 - [224] = https://curl.se/bug/?i=22621 - [225] = https://curl.se/bug/?i=22649 - [226] = https://curl.se/bug/?i=22660 - [227] = https://curl.se/bug/?i=22675 - [228] = https://curl.se/bug/?i=22664 - [229] = https://curl.se/bug/?i=22662 - [230] = https://curl.se/bug/?i=22466 - [231] = https://curl.se/bug/?i=22613 - [232] = https://curl.se/bug/?i=22612 - [233] = https://curl.se/bug/?i=22611 - [234] = https://curl.se/bug/?i=22550 - [235] = https://curl.se/bug/?i=22587 - [236] = https://curl.se/bug/?i=22607 - [237] = https://curl.se/bug/?i=22229 - [238] = https://curl.se/bug/?i=22383 - [239] = https://curl.se/bug/?i=22658 - [240] = https://curl.se/bug/?i=22656 - [241] = https://curl.se/bug/?i=22695 - [242] = https://curl.se/bug/?i=22717 - [243] = https://curl.se/bug/?i=22651 - [244] = https://curl.se/bug/?i=22718 - [245] = https://curl.se/bug/?i=22719 - [246] = https://curl.se/bug/?i=22715 - [247] = https://curl.se/bug/?i=22713 - [248] = https://curl.se/bug/?i=22692 - [249] = https://curl.se/bug/?i=22710 - [250] = https://curl.se/bug/?i=22706 - [251] = https://curl.se/bug/?i=22708 - [252] = https://curl.se/bug/?i=22690 - [253] = https://curl.se/bug/?i=22774 - [254] = https://curl.se/bug/?i=22709 - [255] = https://curl.se/bug/?i=22705 - [256] = https://curl.se/bug/?i=22702 - [257] = https://curl.se/bug/?i=22680 - [258] = https://curl.se/bug/?i=22699 - [259] = https://curl.se/bug/?i=22700 - [260] = https://curl.se/bug/?i=22698 - [261] = https://curl.se/bug/?i=22671 - [262] = https://curl.se/bug/?i=22696 - [263] = https://curl.se/bug/?i=22694 - [264] = https://curl.se/bug/?i=22683 - [265] = https://curl.se/bug/?i=22689 - [268] = https://curl.se/bug/?i=22687 - [269] = https://curl.se/bug/?i=22643 - [270] = https://curl.se/bug/?i=22681 - [271] = https://curl.se/bug/?i=22768 - [272] = https://curl.se/bug/?i=22776 - [273] = https://curl.se/bug/?i=22685 - [274] = https://curl.se/bug/?i=22682 - [275] = https://curl.se/bug/?i=22586 - [276] = https://curl.se/bug/?i=22669 - [277] = https://curl.se/bug/?i=22667 - [278] = https://curl.se/bug/?i=22679 - [279] = https://curl.se/bug/?i=22758 - [280] = https://curl.se/bug/?i=22767 - [281] = https://curl.se/bug/?i=22763 - [282] = https://curl.se/bug/?i=22739 - [284] = https://curl.se/bug/?i=22754 - [286] = https://curl.se/bug/?i=22761 - [288] = https://curl.se/bug/?i=22714 - [289] = https://curl.se/bug/?i=22759 - [290] = https://curl.se/bug/?i=22757 - [291] = https://curl.se/bug/?i=22751 - [292] = https://curl.se/bug/?i=22750 - [295] = https://curl.se/bug/?i=22739 - [296] = https://curl.se/bug/?i=22738 - [297] = https://curl.se/bug/?i=22737 - [301] = https://curl.se/bug/?i=22726 - [304] = https://curl.se/bug/?i=22730 - [305] = https://curl.se/bug/?i=22686 - [306] = https://curl.se/bug/?i=22725 - [307] = https://curl.se/bug/?i=22716 - [308] = https://curl.se/bug/?i=22635 + [2] = https://curl.se/bug/?i=22799 + [3] = https://curl.se/bug/?i=22811 + [7] = https://curl.se/bug/?i=22788 + [8] = https://curl.se/bug/?i=22797 + [9] = https://curl.se/bug/?i=22798 + [11] = https://curl.se/bug/?i=22791 + [12] = https://curl.se/bug/?i=22795 + [13] = https://curl.se/bug/?i=22786 diff --git a/include/curl/curlver.h b/include/curl/curlver.h index 98d10c4fc2b5..65390e52fe80 100644 --- a/include/curl/curlver.h +++ b/include/curl/curlver.h @@ -32,13 +32,13 @@ /* This is the version number of the libcurl package from which this header file origins: */ -#define LIBCURL_VERSION "8.22.0-DEV" +#define LIBCURL_VERSION "8.22.1-DEV" /* The numeric version number is also available "in parts" by using these defines: */ #define LIBCURL_VERSION_MAJOR 8 #define LIBCURL_VERSION_MINOR 22 -#define LIBCURL_VERSION_PATCH 0 +#define LIBCURL_VERSION_PATCH 1 /* This is the numeric version of the libcurl version number, meant for easier parsing and comparisons by programs. The LIBCURL_VERSION_NUM define always follows this syntax: @@ -57,7 +57,7 @@ Note: This define is the full hex number and _does not_ use the CURL_VERSION_BITS() macro since curl's own configure script greps for it and needs it to contain the full number. */ -#define LIBCURL_VERSION_NUM 0x081600 +#define LIBCURL_VERSION_NUM 0x081601 /* * This is the date and time when the full source package was created. The From 30107badd5aa8569cf6005ff6a8adb81c25a4a17 Mon Sep 17 00:00:00 2001 From: Daniel Stenberg Date: Tue, 1 Sep 2026 17:36:28 +0200 Subject: [PATCH 014/481] tool_cb_wrt: try up to .9999 when no-clobbering Previously it gave up after 100. Some users use this feature when downloading large number of files in a glob range and then 100 is on the small side. Tweaked test 1683 to verify this even with the new limit. Closes #22784 --- docs/cmdline-opts/no-clobber.md | 2 +- src/tool_cb_wrt.c | 18 ++++++++++++++++-- tests/data/test1683 | 4 ++++ 3 files changed, 21 insertions(+), 3 deletions(-) diff --git a/docs/cmdline-opts/no-clobber.md b/docs/cmdline-opts/no-clobber.md index 02a74b25e4d4..f7b08855ef31 100644 --- a/docs/cmdline-opts/no-clobber.md +++ b/docs/cmdline-opts/no-clobber.md @@ -18,7 +18,7 @@ Example: When used in conjunction with the --output, --remote-header-name, --remote-name, or --remote-name-all options, curl avoids overwriting files that already exist. Instead, a dot and a number gets appended to the name of -the file that would be created, up to filename.100 after which it does not +the file that would be created, up to filename.9999 after which it does not create any file. Note that this is the negated option name documented. You can thus use diff --git a/src/tool_cb_wrt.c b/src/tool_cb_wrt.c index c67e3975d2d1..ffaf0b76db3c 100644 --- a/src/tool_cb_wrt.c +++ b/src/tool_cb_wrt.c @@ -34,6 +34,8 @@ #define OPENMODE (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH) #endif +#define MAX_CLOBBER_ATTEMPTS 9999 + /* create/open a local file for writing, return TRUE on success */ bool tool_create_output_file(struct OutStruct *outs, struct OperationConfig *config) @@ -62,12 +64,24 @@ bool tool_create_output_file(struct OutStruct *outs, int next_num = 1; struct dynbuf fbuffer; char *newfile; + int max_num = MAX_CLOBBER_ATTEMPTS; +#ifdef DEBUGBUILD + char *p = curl_getenv("CURL_MAX_CLOBBER"); + if(p) { + const char *pp = p; + curl_off_t val; + if(!curlx_str_number(&pp, &val, 100000)) + max_num = (int)val; + curl_free(p); + } +#endif curlx_dyn_init(&fbuffer, 1025); /* !checksrc! disable ERRNOVAR 1 */ while(fd == -1 && /* have not successfully opened a file */ - (errno == EEXIST || errno == EISDIR) && /* because we keep having files that already exist */ - next_num < 100 /* and we have not reached the retry limit */) { + (errno == EEXIST || errno == EISDIR) && + /* and we have not reached the retry limit */ + (next_num < max_num)) { curlx_dyn_reset(&fbuffer); if(curlx_dyn_addf(&fbuffer, "%s.%d", fname, next_num)) return FALSE; diff --git a/tests/data/test1683 b/tests/data/test1683 index 5a01be74dc8b..ff86f6017fd0 100644 --- a/tests/data/test1683 +++ b/tests/data/test1683 @@ -30,7 +30,11 @@ http http +Debug + +CURL_MAX_CLOBBER=100 + http://%HOSTIP:%HTTPPORT/%TESTNUMBER -o %LOGDIR/exist%TESTNUMBER --no-clobber From 5bb0bea0845042109f3f1d201861d249f817cf12 Mon Sep 17 00:00:00 2001 From: Mihai-Gabriel Marin Date: Sun, 30 Aug 2026 22:03:45 +0200 Subject: [PATCH 015/481] runtests: Added runnernums in order to have O(1) lookup This pull request adds runnernums the reverse of runnerids. In this way the iteration over runnerids is no longer needed. The check can happen directly in runnernums. Signed-off-by: Mihai-Gabriel Marin Closes #22753 --- tests/runtests.pl | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/tests/runtests.pl b/tests/runtests.pl index b5a2b4df9cd2..98f469687ce3 100755 --- a/tests/runtests.pl +++ b/tests/runtests.pl @@ -173,6 +173,7 @@ BEGIN my %singletest_logs; # log messages while in singletest array ref by runner my $singletest_bufferedrunner; # runner ID which is buffering logs my %runnerids; # runner IDs by number +my %runnernums; # runner numbers by IDs (inverted %runnerids) my @runnersidle; # runner IDs idle and ready to execute a test my %countforrunner; # test count by runner ID my %runnersrunning; # tests currently running by runner ID @@ -269,11 +270,8 @@ sub catch_usr1 { print scalar(%runnersrunning) . " busy test runner(s) of " . scalar(keys %runnerids) . "\r\n"; foreach my $rid (sort(keys(%runnersrunning))) { my $runnernum = "unknown"; - foreach my $rnum (keys %runnerids) { - if($runnerids{$rnum} == $rid) { - $runnernum = $rnum; - last; - } + if(exists $runnernums{$rid}) { + $runnernum = $runnernums{$rid}; } print "Runner $runnernum (id $rid) running test $runnersrunning{$rid} in state $singletest_state{$rid}\r\n"; } @@ -1041,11 +1039,8 @@ sub getrunnerlogdir { if($jobs <= 1) { return $LOGDIR; } - # TODO: speed up this O(n) operation - for my $runnernum (keys %runnerids) { - if($runnerid eq $runnerids{$runnernum}) { - return "$LOGDIR/$runnernum"; - } + if(exists $runnernums{$runnerid}) { + return "$LOGDIR/$runnernums{$runnerid}"; } die "Internal error: runner ID $runnerid not found"; } @@ -2320,8 +2315,10 @@ sub createrunners { my $dir = getrunnernumlogdir($runnernum); cleardir($dir); mkdir($dir, 0777); - $runnerids{$runnernum} = runner_init($dir, $jobs); - runnerready($runnerids{$runnernum}); + my $id = runner_init($dir, $jobs); + $runnerids{$runnernum} = $id; + $runnernums{$id} = $runnernum; + runnerready($id); } } From b01224a3774c17959cbaf805fc4f79ffe90398c9 Mon Sep 17 00:00:00 2001 From: Max Dymond Date: Wed, 2 Sep 2026 16:56:24 +0100 Subject: [PATCH 016/481] multi_ntfy: replace enabled bitset with inline flags Store enabled notification types in the low bits of a uint32_t and use the high bit for the pending-entry state. This reduces curl_multi_ntfy to 40 bytes and removes an allocation from multi-handle initialization. Closes #22804 --- include/curl/multi.h | 1 + lib/multi.c | 3 +-- lib/multi_ntfy.c | 31 ++++++++++++++----------------- lib/multi_ntfy.h | 11 ++++++----- 4 files changed, 22 insertions(+), 24 deletions(-) diff --git a/include/curl/multi.h b/include/curl/multi.h index 8cef6e774e46..6a79e0a8bb2f 100644 --- a/include/curl/multi.h +++ b/include/curl/multi.h @@ -538,6 +538,7 @@ CURL_EXTERN CURLMcode curl_multi_waitfds(CURLM *m, */ #define CURLMNOTIFY_INFO_READ 0 #define CURLMNOTIFY_EASY_DONE 1 +#define CURLMNOTIFY_LAST 2 /* last, not used */ /* * Callback to install via CURLMOPT_NOTIFYFUNCTION. diff --git a/lib/multi.c b/lib/multi.c index fbefa6f30d91..ce303b40f316 100644 --- a/lib/multi.c +++ b/lib/multi.c @@ -276,8 +276,7 @@ struct Curl_multi *Curl_multi_handle(uint32_t xfer_table_size, multi->wakeup_internal[1] = CURL_SOCKET_BAD; #endif - if(Curl_mntfy_resize(multi) || - Curl_uint32_bset_resize(&multi->process, xfer_table_size) || + if(Curl_uint32_bset_resize(&multi->process, xfer_table_size) || Curl_uint32_bset_resize(&multi->pending, xfer_table_size) || Curl_uint32_bset_resize(&multi->dirty, xfer_table_size) || Curl_uint32_bset_resize(&multi->msgsent, xfer_table_size) || diff --git a/lib/multi_ntfy.c b/lib/multi_ntfy.c index 48c37239519a..991259dadfb3 100644 --- a/lib/multi_ntfy.c +++ b/lib/multi_ntfy.c @@ -29,6 +29,11 @@ #include "multiif.h" #include "multi_ntfy.h" +#if CURLMNOTIFY_LAST > 31 +#error "notification types must fit below the curl_multi_ntfy high flag bit" +#endif + +#define CURL_MNTFY_TYPE_FLAG(type) ((uint32_t)1 << (type)) struct mntfy_entry { uint32_t mid; @@ -108,7 +113,7 @@ static void mntfy_chunk_dispatch_all(struct Curl_multi *multi, e = &chunk->entries[chunk->r_offset]; data = e->mid ? Curl_multi_get_easy(multi, e->mid) : multi->admin; /* only when notification has not been disabled in the meantime */ - if(data && Curl_uint32_bset_contains(&multi->ntfy.enabled, e->type)) { + if(data && (multi->ntfy.flags & CURL_MNTFY_TYPE_FLAG(e->type))) { /* this may cause new notifications to be added! */ CURL_TRC_M(multi->admin, "[NTFY] dispatch %u to xfer %u", e->type, e->mid); @@ -124,14 +129,6 @@ static void mntfy_chunk_dispatch_all(struct Curl_multi *multi, void Curl_mntfy_init(struct Curl_multi *multi) { memset(&multi->ntfy, 0, sizeof(multi->ntfy)); - Curl_uint32_bset_init(&multi->ntfy.enabled); -} - -CURLMcode Curl_mntfy_resize(struct Curl_multi *multi) -{ - if(Curl_uint32_bset_resize(&multi->ntfy.enabled, CURLMNOTIFY_EASY_DONE + 1)) - return CURLM_OUT_OF_MEMORY; - return CURLM_OK; } void Curl_mntfy_cleanup(struct Curl_multi *multi) @@ -142,22 +139,21 @@ void Curl_mntfy_cleanup(struct Curl_multi *multi) mnfty_chunk_destroy(chunk); } multi->ntfy.tail = NULL; - Curl_uint32_bset_destroy(&multi->ntfy.enabled); } CURLMcode Curl_mntfy_enable(struct Curl_multi *multi, unsigned int type) { - if(type > CURLMNOTIFY_EASY_DONE) + if(type >= CURLMNOTIFY_LAST) return CURLM_UNKNOWN_OPTION; - Curl_uint32_bset_add(&multi->ntfy.enabled, type); + multi->ntfy.flags |= CURL_MNTFY_TYPE_FLAG(type); return CURLM_OK; } CURLMcode Curl_mntfy_disable(struct Curl_multi *multi, unsigned int type) { - if(type > CURLMNOTIFY_EASY_DONE) + if(type >= CURLMNOTIFY_LAST) return CURLM_UNKNOWN_OPTION; - Curl_uint32_bset_remove(&multi->ntfy.enabled, (uint32_t)type); + multi->ntfy.flags &= ~CURL_MNTFY_TYPE_FLAG(type); return CURLM_OK; } @@ -165,7 +161,8 @@ void Curl_mntfy_add(struct Curl_easy *data, unsigned int type) { struct Curl_multi *multi = data ? data->multi : NULL; if(multi && multi->ntfy.ntfy_cb && !multi->ntfy.failure && - Curl_uint32_bset_contains(&multi->ntfy.enabled, (uint32_t)type)) { + type < CURLMNOTIFY_LAST && + (multi->ntfy.flags & CURL_MNTFY_TYPE_FLAG(type))) { /* append to list of outstanding notifications */ struct mntfy_chunk *tail = mntfy_non_full_tail(&multi->ntfy); CURL_TRC_M(data, "[NTFY] add %u for xfer %u", type, data->mid); @@ -173,7 +170,7 @@ void Curl_mntfy_add(struct Curl_easy *data, unsigned int type) mntfy_chunk_append(tail, data, (uint32_t)type); else multi->ntfy.failure = CURLM_OUT_OF_MEMORY; - multi->ntfy.has_entries = TRUE; + multi->ntfy.flags |= CURL_MNTFY_FLAG_HAS_ENTRIES; } } @@ -208,6 +205,6 @@ CURLMcode Curl_mntfy_dispatch_all(struct Curl_multi *multi) return mresult; } else - multi->ntfy.has_entries = FALSE; + multi->ntfy.flags &= ~CURL_MNTFY_FLAG_HAS_ENTRIES; return CURLM_OK; } diff --git a/lib/multi_ntfy.h b/lib/multi_ntfy.h index 05df2d6681b6..acdd6bd92c2e 100644 --- a/lib/multi_ntfy.h +++ b/lib/multi_ntfy.h @@ -23,7 +23,9 @@ * SPDX-License-Identifier: curl * ***************************************************************************/ -#include "uint-bset.h" +#include "curl_setup.h" + +#define CURL_MNTFY_FLAG_HAS_ENTRIES ((uint32_t)1 << 31) struct Curl_easy; struct Curl_multi; @@ -31,15 +33,13 @@ struct Curl_multi; struct curl_multi_ntfy { curl_notify_callback ntfy_cb; void *ntfy_cb_data; - struct uint32_bset enabled; struct mntfy_chunk *head; struct mntfy_chunk *tail; + uint32_t flags; CURLMcode failure; - BIT(has_entries); }; void Curl_mntfy_init(struct Curl_multi *multi); -CURLMcode Curl_mntfy_resize(struct Curl_multi *multi); void Curl_mntfy_cleanup(struct Curl_multi *multi); CURLMcode Curl_mntfy_enable(struct Curl_multi *multi, unsigned int type); @@ -53,7 +53,8 @@ void Curl_mntfy_add(struct Curl_easy *data, unsigned int type); Curl_mntfy_add((d), (t)); \ } while(0) -#define CURL_MNTFY_HAS_ENTRIES(m) ((m)->ntfy.has_entries) +#define CURL_MNTFY_HAS_ENTRIES(m) \ + ((m)->ntfy.flags & CURL_MNTFY_FLAG_HAS_ENTRIES) CURLMcode Curl_mntfy_dispatch_all(struct Curl_multi *multi); From fcde6334507993cc1edb39922aae34dcc95d0add Mon Sep 17 00:00:00 2001 From: Viktor Szakats Date: Mon, 31 Aug 2026 00:01:17 +0200 Subject: [PATCH 017/481] global_init: tidy up, simplify, fix issues - ssh: deinit before tearing down Winsock. (also to sync with reverse init order.) - winsock: fix to deinitialize if any of the subsequent initialization fails. - win32: init version info and clock before anything else. To make both usable by init code and also because these inits cannot fail. - win32: move init logic from system_win32.c back into easy.c, drop the umbrella functions to allow more granular control over init order, to simplify, and expose the context. - sspi: init before Winsock, to avoid a cycling the latter in case of an init failure. - trc: init after win32 version info and clock but before others. To allow tracing during initialization. - async: sync error message with internal function name. Bug: https://github.com/curl/curl/pull/22746/files#discussion_r3889939149 Closes #22756 --- lib/Makefile.inc | 2 -- lib/easy.c | 74 ++++++++++++++++++++++++++++++-------------- lib/system_win32.c | 76 ---------------------------------------------- lib/system_win32.h | 35 --------------------- 4 files changed, 52 insertions(+), 135 deletions(-) delete mode 100644 lib/system_win32.c delete mode 100644 lib/system_win32.h diff --git a/lib/Makefile.inc b/lib/Makefile.inc index c4e26849aa20..42ebc8b274f3 100644 --- a/lib/Makefile.inc +++ b/lib/Makefile.inc @@ -284,7 +284,6 @@ LIB_CFILES = \ strcase.c \ strequal.c \ strerror.c \ - system_win32.c \ telnet.c \ tftp.c \ thrdpool.c \ @@ -418,7 +417,6 @@ LIB_HFILES = \ splay.h \ strcase.h \ strerror.h \ - system_win32.h \ telnet.h \ tftp.h \ thrdpool.h \ diff --git a/lib/easy.c b/lib/easy.c index 04c5a003a048..5ebd3abae390 100644 --- a/lib/easy.c +++ b/lib/easy.c @@ -70,18 +70,20 @@ #include "vssh/ssh.h" #include "setopt.h" #include "http_digest.h" -#include "system_win32.h" #include "curlx/dynbuf.h" #include "bufref.h" #include "altsvc.h" #include "hsts.h" +#include "curl_sspi.h" /* Curl_sspi_global_init()/Curl_sspi_global_cleanup() */ +#include "curlx/timeval.h" /* for curlx_now_init() */ +#include "curlx/version_win32.h" /* for curlx_verify_windows_init() */ #include "easy_lock.h" /* true globals -- for curl_global_init() and curl_global_cleanup() */ static unsigned int initialized; -#ifdef _WIN32 -static long easy_init_flags; +#ifdef USE_WINSOCK +static bool winsock_initialized; #endif #ifdef GLOBAL_INIT_IS_THREADSAFE @@ -126,6 +128,8 @@ static char *leakpointer; */ static CURLcode global_init(long flags, bool memoryfuncs) { + (void)flags; + if(initialized++) return CURLE_OK; @@ -138,16 +142,40 @@ static CURLcode global_init(long flags, bool memoryfuncs) Curl_ccalloc = (curl_calloc_callback)calloc; } - if(Curl_win32_init(flags)) { - DEBUGF(curl_mfprintf(stderr, "Error: win32_init failed\n")); - goto fail; - } +#ifdef _WIN32 + curlx_verify_windows_init(); + curlx_now_init(); +#endif if(Curl_trc_init()) { DEBUGF(curl_mfprintf(stderr, "Error: Curl_trc_init failed\n")); goto fail; } +#ifdef USE_WINDOWS_SSPI + if(Curl_sspi_global_init()) { + DEBUGF(curl_mfprintf(stderr, "Error: Curl_sspi_global_init() failed\n")); + goto fail; + } +#endif + +#ifdef _WIN32 + /* CURL_GLOBAL_WIN32 controls the *optional* part of the initialization which + is for Winsock at the moment. */ + if(flags & CURL_GLOBAL_WIN32) { +#ifdef USE_WINSOCK + WSADATA wsa; + if(WSAStartup(MAKEWORD(2, 2), &wsa)) { + DEBUGF(curl_mfprintf(stderr, "Error: WSAStartup() failed\n")); + goto fail; + } + winsock_initialized = TRUE; +#elif defined(USE_LWIPSOCK) + lwip_init(); +#endif + } +#endif + if(!Curl_ssl_init()) { DEBUGF(curl_mfprintf(stderr, "Error: Curl_ssl_init failed\n")); goto fail; @@ -178,12 +206,6 @@ static CURLcode global_init(long flags, bool memoryfuncs) goto fail; } -#ifdef _WIN32 - easy_init_flags = flags; -#else - (void)flags; -#endif - #ifdef DEBUGBUILD if(getenv("CURL_GLOBAL_INIT")) /* alloc data that will leak if *cleanup() is not called! */ @@ -193,6 +215,12 @@ static CURLcode global_init(long flags, bool memoryfuncs) return CURLE_OK; fail: +#ifdef USE_WINSOCK + if(winsock_initialized) { + WSACleanup(); + winsock_initialized = FALSE; + } +#endif initialized--; /* undo the increase */ return CURLE_FAILED_INIT; } @@ -255,9 +283,7 @@ CURLcode curl_global_init_mem(long flags, curl_malloc_callback m, } /** - * curl_global_cleanup() globally cleanups curl, uses the value of - * "easy_init_flags" to determine what needs to be cleaned up and what does - * not. + * curl_global_cleanup() globally cleanups curl. */ void curl_global_cleanup(void) { @@ -273,18 +299,22 @@ void curl_global_cleanup(void) return; } + Curl_ssh_cleanup(); Curl_ssl_cleanup(); Curl_vquic_cleanup(); Curl_async_global_cleanup(); + Curl_amiga_cleanup(); -#ifdef _WIN32 - Curl_win32_cleanup(easy_init_flags); - easy_init_flags = 0; +#ifdef USE_WINSOCK + if(winsock_initialized) { + WSACleanup(); + winsock_initialized = FALSE; + } #endif - Curl_amiga_cleanup(); - - Curl_ssh_cleanup(); +#ifdef USE_WINDOWS_SSPI + Curl_sspi_global_cleanup(); +#endif #ifdef DEBUGBUILD curlx_free(leakpointer); diff --git a/lib/system_win32.c b/lib/system_win32.c deleted file mode 100644 index 4cbc2c25e9bf..000000000000 --- a/lib/system_win32.c +++ /dev/null @@ -1,76 +0,0 @@ -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) Steve Holme, . - * - * This software is licensed as described in the file COPYING, which - * you should have received as part of this distribution. The terms - * are also available at https://curl.se/docs/copyright.html. - * - * You may opt to use, copy, modify, merge, publish, distribute and/or sell - * copies of the Software, and permit persons to whom the Software is - * furnished to do so, under the terms of the COPYING file. - * - * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY - * KIND, either express or implied. - * - * SPDX-License-Identifier: curl - * - ***************************************************************************/ -#include "curl_setup.h" - -#ifdef _WIN32 - -#include "system_win32.h" -#include "curl_sspi.h" -#include "curlx/timeval.h" -#include "curlx/version_win32.h" /* for curlx_verify_windows_init() */ - -/* Curl_win32_init() performs Win32 global initialization */ -CURLcode Curl_win32_init(long flags) -{ - /* CURL_GLOBAL_WIN32 controls the *optional* part of the initialization which - is for Winsock at the moment. Any required Win32 initialization - should take place after this block. */ - if(flags & CURL_GLOBAL_WIN32) { -#ifdef USE_WINSOCK - WSADATA wsa; - if(WSAStartup(MAKEWORD(2, 2), &wsa)) - return CURLE_FAILED_INIT; -#elif defined(USE_LWIPSOCK) - lwip_init(); -#endif - } /* CURL_GLOBAL_WIN32 */ - -#ifdef USE_WINDOWS_SSPI - { - CURLcode result = Curl_sspi_global_init(); - if(result) - return result; - } -#endif - - curlx_verify_windows_init(); - curlx_now_init(); - return CURLE_OK; -} - -/* Curl_win32_cleanup() is the opposite of Curl_win32_init() */ -void Curl_win32_cleanup(long init_flags) -{ -#ifdef USE_WINDOWS_SSPI - Curl_sspi_global_cleanup(); -#endif - - if(init_flags & CURL_GLOBAL_WIN32) { -#ifdef USE_WINSOCK - WSACleanup(); -#endif - } -} - -#endif /* _WIN32 */ diff --git a/lib/system_win32.h b/lib/system_win32.h deleted file mode 100644 index d504b563db4b..000000000000 --- a/lib/system_win32.h +++ /dev/null @@ -1,35 +0,0 @@ -#ifndef HEADER_CURL_SYSTEM_WIN32_H -#define HEADER_CURL_SYSTEM_WIN32_H -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) Steve Holme, . - * - * This software is licensed as described in the file COPYING, which - * you should have received as part of this distribution. The terms - * are also available at https://curl.se/docs/copyright.html. - * - * You may opt to use, copy, modify, merge, publish, distribute and/or sell - * copies of the Software, and permit persons to whom the Software is - * furnished to do so, under the terms of the COPYING file. - * - * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY - * KIND, either express or implied. - * - * SPDX-License-Identifier: curl - * - ***************************************************************************/ -#include "curl_setup.h" - -#ifdef _WIN32 -CURLcode Curl_win32_init(long flags); -void Curl_win32_cleanup(long init_flags); -#else -#define Curl_win32_init(x) CURLE_OK -#endif /* _WIN32 */ - -#endif /* HEADER_CURL_SYSTEM_WIN32_H */ From 843434b486066f106cff787bc77d42caa2785dfe Mon Sep 17 00:00:00 2001 From: Viktor Szakats Date: Wed, 2 Sep 2026 08:11:52 +0200 Subject: [PATCH 018/481] warnless: dedupe Intel compiler warning suppression pragmas Closes #22796 --- lib/curlx/warnless.c | 111 +++---------------------------------------- 1 file changed, 6 insertions(+), 105 deletions(-) diff --git a/lib/curlx/warnless.c b/lib/curlx/warnless.c index 5fc36ac9a876..be26650bb41a 100644 --- a/lib/curlx/warnless.c +++ b/lib/curlx/warnless.c @@ -48,22 +48,18 @@ #define CURL_MASK_USIZE_T ((size_t)~0) #define CURL_MASK_SSIZE_T (CURL_MASK_USIZE_T >> 1) -/* - * unsigned long to unsigned char - */ -unsigned char curlx_ultouc(unsigned long ulnum) -{ #ifdef __INTEL_COMPILER #pragma warning(push) #pragma warning(disable:810) /* conversion may lose significant bits */ #endif +/* + * unsigned long to unsigned char + */ +unsigned char curlx_ultouc(unsigned long ulnum) +{ DEBUGASSERT(ulnum <= (unsigned long)CURL_MASK_UCHAR); return (unsigned char)(ulnum & (unsigned long)CURL_MASK_UCHAR); - -#ifdef __INTEL_COMPILER -#pragma warning(pop) -#endif } /* @@ -71,17 +67,8 @@ unsigned char curlx_ultouc(unsigned long ulnum) */ int curlx_uztosi(size_t uznum) { -#ifdef __INTEL_COMPILER -#pragma warning(push) -#pragma warning(disable:810) /* conversion may lose significant bits */ -#endif - DEBUGASSERT(uznum <= (size_t)CURL_MASK_SINT); return (int)(uznum & (size_t)CURL_MASK_SINT); - -#ifdef __INTEL_COMPILER -#pragma warning(pop) -#endif } /* @@ -89,19 +76,10 @@ int curlx_uztosi(size_t uznum) */ unsigned long curlx_uztoul(size_t uznum) { -#ifdef __INTEL_COMPILER -#pragma warning(push) -#pragma warning(disable:810) /* conversion may lose significant bits */ -#endif - #if ULONG_MAX < SIZE_MAX DEBUGASSERT(uznum <= (size_t)CURL_MASK_ULONG); #endif return (unsigned long)(uznum & (size_t)CURL_MASK_ULONG); - -#ifdef __INTEL_COMPILER -#pragma warning(pop) -#endif } /* @@ -109,19 +87,10 @@ unsigned long curlx_uztoul(size_t uznum) */ unsigned int curlx_uztoui(size_t uznum) { -#ifdef __INTEL_COMPILER -#pragma warning(push) -#pragma warning(disable:810) /* conversion may lose significant bits */ -#endif - #if UINT_MAX < SIZE_MAX DEBUGASSERT(uznum <= (size_t)CURL_MASK_UINT); #endif return (unsigned int)(uznum & (size_t)CURL_MASK_UINT); - -#ifdef __INTEL_COMPILER -#pragma warning(pop) -#endif } /* @@ -129,20 +98,11 @@ unsigned int curlx_uztoui(size_t uznum) */ int curlx_sltosi(long slnum) { -#ifdef __INTEL_COMPILER -#pragma warning(push) -#pragma warning(disable:810) /* conversion may lose significant bits */ -#endif - DEBUGASSERT(slnum >= 0); #if INT_MAX < LONG_MAX DEBUGASSERT((unsigned long)slnum <= (unsigned long)CURL_MASK_SINT); #endif return (int)(slnum & (long)CURL_MASK_SINT); - -#ifdef __INTEL_COMPILER -#pragma warning(pop) -#endif } /* @@ -150,20 +110,11 @@ int curlx_sltosi(long slnum) */ unsigned int curlx_sltoui(long slnum) { -#ifdef __INTEL_COMPILER -#pragma warning(push) -#pragma warning(disable:810) /* conversion may lose significant bits */ -#endif - DEBUGASSERT(slnum >= 0); #if UINT_MAX < LONG_MAX DEBUGASSERT((unsigned long)slnum <= (unsigned long)CURL_MASK_UINT); #endif return (unsigned int)(slnum & (long)CURL_MASK_UINT); - -#ifdef __INTEL_COMPILER -#pragma warning(pop) -#endif } /* @@ -171,18 +122,9 @@ unsigned int curlx_sltoui(long slnum) */ unsigned short curlx_sltous(long slnum) { -#ifdef __INTEL_COMPILER -#pragma warning(push) -#pragma warning(disable:810) /* conversion may lose significant bits */ -#endif - DEBUGASSERT(slnum >= 0); DEBUGASSERT((unsigned long)slnum <= (unsigned long)CURL_MASK_USHORT); return (unsigned short)(slnum & (long)CURL_MASK_USHORT); - -#ifdef __INTEL_COMPILER -#pragma warning(pop) -#endif } /* @@ -190,17 +132,8 @@ unsigned short curlx_sltous(long slnum) */ ssize_t curlx_uztosz(size_t uznum) { -#ifdef __INTEL_COMPILER -#pragma warning(push) -#pragma warning(disable:810) /* conversion may lose significant bits */ -#endif - DEBUGASSERT(uznum <= (size_t)CURL_MASK_SSIZE_T); return (ssize_t)(uznum & (size_t)CURL_MASK_SSIZE_T); - -#ifdef __INTEL_COMPILER -#pragma warning(pop) -#endif } /* @@ -208,17 +141,8 @@ ssize_t curlx_uztosz(size_t uznum) */ size_t curlx_sotouz(curl_off_t sonum) { -#ifdef __INTEL_COMPILER -#pragma warning(push) -#pragma warning(disable:810) /* conversion may lose significant bits */ -#endif - DEBUGASSERT(sonum >= 0); return (size_t)(sonum & (curl_off_t)CURL_MASK_USIZE_T); - -#ifdef __INTEL_COMPILER -#pragma warning(pop) -#endif } /* @@ -226,20 +150,11 @@ size_t curlx_sotouz(curl_off_t sonum) */ int curlx_sztosi(ssize_t sznum) { -#ifdef __INTEL_COMPILER -#pragma warning(push) -#pragma warning(disable:810) /* conversion may lose significant bits */ -#endif - DEBUGASSERT(sznum >= 0); #if INT_MAX < SSIZE_MAX DEBUGASSERT((size_t)sznum <= (size_t)CURL_MASK_SINT); #endif return (int)(sznum & (ssize_t)CURL_MASK_SINT); - -#ifdef __INTEL_COMPILER -#pragma warning(pop) -#endif } /* @@ -247,17 +162,8 @@ int curlx_sztosi(ssize_t sznum) */ unsigned short curlx_uitous(unsigned int uinum) { -#ifdef __INTEL_COMPILER -#pragma warning(push) -#pragma warning(disable:810) /* conversion may lose significant bits */ -#endif - DEBUGASSERT(uinum <= (unsigned int)CURL_MASK_USHORT); return (unsigned short)(uinum & (unsigned int)CURL_MASK_USHORT); - -#ifdef __INTEL_COMPILER -#pragma warning(pop) -#endif } /* @@ -265,18 +171,13 @@ unsigned short curlx_uitous(unsigned int uinum) */ size_t curlx_sitouz(int sinum) { -#ifdef __INTEL_COMPILER -#pragma warning(push) -#pragma warning(disable:810) /* conversion may lose significant bits */ -#endif - DEBUGASSERT(sinum >= 0); return (size_t)sinum; +} #ifdef __INTEL_COMPILER #pragma warning(pop) #endif -} size_t curlx_uitouz(unsigned int uinum) { From 0d68a05cc3dcf750cf36499eb0f0c9856f203a42 Mon Sep 17 00:00:00 2001 From: Viktor Szakats Date: Wed, 2 Sep 2026 23:39:10 +0200 Subject: [PATCH 019/481] curl_addrinfo: drop redundant parentheses Closes #22810 --- lib/curl_addrinfo.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/curl_addrinfo.c b/lib/curl_addrinfo.c index 98ba7aec1c1f..d578ab727545 100644 --- a/lib/curl_addrinfo.c +++ b/lib/curl_addrinfo.c @@ -319,7 +319,7 @@ struct Curl_addrinfo *Curl_he2ai(const struct hostent *he, int port) addr = (void *)ai->ai_addr; /* storage area for this info */ memcpy(&addr->sin_addr, curr, sizeof(struct in_addr)); - addr->sin_family = (CURL_SA_FAMILY_T)(he->h_addrtype); + addr->sin_family = (CURL_SA_FAMILY_T)he->h_addrtype; addr->sin_port = htons((unsigned short)port); break; @@ -328,7 +328,7 @@ struct Curl_addrinfo *Curl_he2ai(const struct hostent *he, int port) addr6 = (void *)ai->ai_addr; /* storage area for this info */ memcpy(&addr6->sin6_addr, curr, sizeof(struct in6_addr)); - addr6->sin6_family = (CURL_SA_FAMILY_T)(he->h_addrtype); + addr6->sin6_family = (CURL_SA_FAMILY_T)he->h_addrtype; addr6->sin6_port = htons((unsigned short)port); break; #endif From b97cef6c0871310a2ebd9a7c12951a16411ac397 Mon Sep 17 00:00:00 2001 From: Viktor Szakats Date: Thu, 3 Sep 2026 09:48:17 +0200 Subject: [PATCH 020/481] appveyor: fix description, drop redundant env - drop redundant Unicode setting. Follow-up to 41198e09b66bd5f99fcec9fe9f0d562f70559309 #20390 - sync OpenSSL version in description with actual. Follow-up to 81cdf4d8e5be60bf3fe498c823c34a42f2b08294 #21939 Reported-by: Jay Satiro Ref: #22653 Closes #22812 --- appveyor.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index a08285efc523..6366dff9a5ff 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -88,7 +88,7 @@ environment: CMAKE_GENERATOR: 'Visual Studio 16 2019' CMAKE_GENERATE: '-A x64 -DCURL_USE_OPENSSL=ON -DCURL_DISABLE_VERBOSE_STRINGS=ON' - - job_name: 'CM VS2026, Debug, x64, OpenSSL 3.5 + Schannel, Static, Unicode, Build-tests & examples, clang-cl' + - job_name: 'CM VS2026, Debug, x64, OpenSSL 3.6 + Schannel, Static, Unicode, Build-tests & examples, clang-cl' APPVEYOR_BUILD_WORKER_IMAGE: 'Visual Studio 2026' CMAKE_VERSION: 4.2.1 CMAKE_SHA256: dfc2b2afac257555e3b9ce375b12b2883964283a366c17fec96cf4d17e4f1677 @@ -98,7 +98,6 @@ environment: - job_name: 'CM VS2022, Release, x64, Schannel, Shared, Unicode, Build-tests' APPVEYOR_BUILD_WORKER_IMAGE: 'Visual Studio 2022' CMAKE_GENERATOR: 'Visual Studio 17 2022' - ENABLE_UNICODE: 'ON' CMAKE_GENERATE: '-A x64 -DENABLE_UNICODE=ON -DENABLE_DEBUG=OFF' - job_name: 'CM VS2026, Debug, x64, !ssl, Static, Build-tests' From ba666ecef61355c51640676aa1b17c97ff4636c0 Mon Sep 17 00:00:00 2001 From: Viktor Szakats Date: Thu, 27 Aug 2026 01:53:04 +0200 Subject: [PATCH 021/481] clang-tidy: merge nested `if()`s Reported by clang-tidy 23.1.0+ via `readability-redundant-nested-if`. Do not enforce at this time, due to a handful of places where it 1) applies conditionally for non-debug builds (cf-h2-proxy.c), 2) decreases readability (cookie.c, probably could be fixed by using temp variables or code refactor), 3) breaks code structure (protocol.c). Ref: https://clang.llvm.org/extra/clang-tidy/checks/readability/redundant-nested-if.html Closes #22793 --- .clang-tidy.yml | 1 + docs/examples/multi-double.c | 8 ++-- docs/examples/websocket.c | 7 ++-- lib/cf-h2-proxy.c | 7 +--- lib/cf-https-connect.c | 15 ++++---- lib/cf-ip-happy.c | 7 ++-- lib/cf-socket.c | 44 +++++++++++----------- lib/connect.c | 6 +-- lib/cshutdn.c | 12 +++--- lib/curl_share.c | 14 +++---- lib/curlx/inet_pton.c | 5 +-- lib/easy.c | 14 +++---- lib/formdata.c | 32 +++++++--------- lib/ftp.c | 13 +++---- lib/http.c | 68 ++++++++++++++++------------------ lib/http_aws_sigv4.c | 18 +++------ lib/imap.c | 48 +++++++++++------------- lib/mime.c | 16 ++++---- lib/multi.c | 8 ++-- lib/pop3.c | 12 +++--- lib/rtsp.c | 28 ++++++-------- lib/select.c | 12 ++---- lib/smtp.c | 20 ++++------ lib/telnet.c | 26 ++++++------- lib/url.c | 41 ++++++++++---------- lib/urlapi.c | 16 ++++---- lib/vtls/openssl.c | 39 ++++++------------- src/config2setopts.c | 6 +-- src/tool_cb_dbg.c | 23 ++++++------ src/tool_cb_wrt.c | 7 ++-- src/tool_formparse.c | 22 +++++------ src/tool_getparam.c | 22 ++++------- src/tool_urlglob.c | 7 ++-- src/tool_writeout.c | 30 ++++++--------- src/var.c | 17 ++++----- tests/libtest/cli_h2_pausing.c | 17 ++++----- tests/libtest/lib1560.c | 12 +++--- tests/libtest/lib3102.c | 27 ++++++-------- tests/libtest/lib530.c | 11 +++--- tests/libtest/lib556.c | 17 ++++----- tests/libtest/lib758.c | 11 +++--- tests/server/socksd.c | 16 ++++---- tests/unit/unit1627.c | 8 ++-- tests/unit/unit1675.c | 15 ++++---- tests/unit/unit2603.c | 10 ++--- 45 files changed, 350 insertions(+), 465 deletions(-) diff --git a/.clang-tidy.yml b/.clang-tidy.yml index c804e86f9ffb..6685094fd333 100644 --- a/.clang-tidy.yml +++ b/.clang-tidy.yml @@ -48,6 +48,7 @@ Checks: - readability-redundant-control-flow - readability-redundant-declaration - readability-redundant-function-ptr-dereference + # readability-redundant-nested-if # would need local suppression in a handful of cases # - readability-redundant-parentheses - readability-redundant-preprocessor - readability-suspicious-call-argument diff --git a/docs/examples/multi-double.c b/docs/examples/multi-double.c index 987f1c05689d..4809abb48e7d 100644 --- a/docs/examples/multi-double.c +++ b/docs/examples/multi-double.c @@ -80,11 +80,9 @@ int main(void) do { msg = curl_multi_info_read(multi, &queued); - if(msg) { - if(msg->msg == CURLMSG_DONE) { - /* a transfer ended */ - fprintf(stderr, "Transfer completed\n"); - } + if(msg && msg->msg == CURLMSG_DONE) { + /* a transfer ended */ + fprintf(stderr, "Transfer completed\n"); } } while(msg); } diff --git a/docs/examples/websocket.c b/docs/examples/websocket.c index 7ae299f4ed6b..ad6fc9e0e827 100644 --- a/docs/examples/websocket.c +++ b/docs/examples/websocket.c @@ -77,10 +77,9 @@ static CURLcode recv_pong(CURL *curl, const char *expected_payload) * in chunks, however. */ if(meta->flags & CURLWS_PONG) { int same = 0; - if(rlen == strlen(expected_payload)) { - if(!memcmp(expected_payload, buffer, rlen)) - same = 1; - } + if(rlen == strlen(expected_payload) && + !memcmp(expected_payload, buffer, rlen)) + same = 1; fprintf(stderr, "ws: received PONG with %s payload back\n", same ? "same" : "different"); } diff --git a/lib/cf-h2-proxy.c b/lib/cf-h2-proxy.c index fde9e1bc799a..e4950862ead0 100644 --- a/lib/cf-h2-proxy.c +++ b/lib/cf-h2-proxy.c @@ -527,11 +527,8 @@ static int proxy_h2_on_frame_recv(nghttp2_session *session, /* Only final status code signals the end of header */ CURL_TRC_CF(data, cf, "[%d] got http status: %d", stream_id, ctx->tunnel.resp->status); - if(!ctx->tunnel.has_final_response) { - if(ctx->tunnel.resp->status / 100 != 1) { - ctx->tunnel.has_final_response = TRUE; - } - } + if(!ctx->tunnel.has_final_response && ctx->tunnel.resp->status / 100 != 1) + ctx->tunnel.has_final_response = TRUE; break; case NGHTTP2_WINDOW_UPDATE: if(CURL_REQ_WANT_SEND(data)) { diff --git a/lib/cf-https-connect.c b/lib/cf-https-connect.c index 2e13a5cc09fe..8ede52621479 100644 --- a/lib/cf-https-connect.c +++ b/lib/cf-https-connect.c @@ -259,14 +259,13 @@ static bool time_to_start_baller2(struct Curl_cfilter *cf, ctx->hard_eyeballs_timeout_ms, ctx->ballers[1].name); return TRUE; } - else if(elapsed_ms >= ctx->soft_eyeballs_timeout_ms) { - if(cf_hc_baller_reply_ms(&ctx->ballers[0], data) < 0) { - CURL_TRC_CF(data, cf, "%s has not seen any data after %" - FMT_TIMEDIFF_T "ms, starting %s", - ctx->ballers[0].name, ctx->soft_eyeballs_timeout_ms, - ctx->ballers[1].name); - return TRUE; - } + else if(elapsed_ms >= ctx->soft_eyeballs_timeout_ms && + cf_hc_baller_reply_ms(&ctx->ballers[0], data) < 0) { + CURL_TRC_CF(data, cf, "%s has not seen any data after %" + FMT_TIMEDIFF_T "ms, starting %s", + ctx->ballers[0].name, ctx->soft_eyeballs_timeout_ms, + ctx->ballers[1].name); + return TRUE; } return FALSE; } diff --git a/lib/cf-ip-happy.c b/lib/cf-ip-happy.c index baca4682f0b0..8271557f8a6e 100644 --- a/lib/cf-ip-happy.c +++ b/lib/cf-ip-happy.c @@ -652,10 +652,9 @@ static int cf_ip_ballers_min_reply_ms(struct cf_ip_ballers *bs, for(a = bs->running; a; a = a->next) { if(a->cf && !a->cf->cft->query(a->cf, data, CF_QUERY_CONNECT_REPLY_MS, - &breply_ms, NULL)) { - if(breply_ms >= 0 && (reply_ms < 0 || breply_ms < reply_ms)) - reply_ms = breply_ms; - } + &breply_ms, NULL) && + breply_ms >= 0 && (reply_ms < 0 || breply_ms < reply_ms)) + reply_ms = breply_ms; } return reply_ms; } diff --git a/lib/cf-socket.c b/lib/cf-socket.c index 870a98ecbd5d..3c84bf24bfe8 100644 --- a/lib/cf-socket.c +++ b/lib/cf-socket.c @@ -688,29 +688,27 @@ static CURLcode bindlocal(struct Curl_easy *data, struct connectdata *conn, if2ip_result_t if2ip_result = IF2IP_NOT_FOUND; #ifdef SO_BINDTODEVICE - if(iface) { - /* - * This binds the local socket to a particular interface. This will - * force even requests to other local interfaces to go out the external - * interface. Only bind to the interface when specified as interface, - * not as a hostname or ip address. - * - * The interface might be a VRF, eg: vrf-blue, which means it cannot be - * converted to an IP address and would fail Curl_if2ip. Try to - * use it straight away. - */ - if(setsockopt(sockfd, SOL_SOCKET, SO_BINDTODEVICE, - iface, (curl_socklen_t)strlen(iface) + 1) == 0) { - /* This is often "errno 1, error: Operation not permitted" if you are - * not running as root or another suitable privileged user. If it - * succeeds it means the parameter was a valid interface and not an IP - * address. Return immediately. - */ - if(!host_input) { - infof(data, "socket successfully bound to interface '%s'", iface); - return CURLE_OK; - } - } + /* + * This binds the local socket to a particular interface. This will + * force even requests to other local interfaces to go out the external + * interface. Only bind to the interface when specified as interface, + * not as a hostname or ip address. + * + * The interface might be a VRF, eg: vrf-blue, which means it cannot be + * converted to an IP address and would fail Curl_if2ip. Try to + * use it straight away. + */ + if(iface && + setsockopt(sockfd, SOL_SOCKET, SO_BINDTODEVICE, + iface, (curl_socklen_t)strlen(iface) + 1) == 0 && + /* This is often "errno 1, error: Operation not permitted" if you are + * not running as root or another suitable privileged user. If it + * succeeds it means the parameter was a valid interface and not an IP + * address. Return immediately. + */ + !host_input) { + infof(data, "socket successfully bound to interface '%s'", iface); + return CURLE_OK; } #endif if(!host_input) { diff --git a/lib/connect.c b/lib/connect.c index cb3647544ef8..e91c2167f982 100644 --- a/lib/connect.c +++ b/lib/connect.c @@ -50,10 +50,8 @@ enum alpnid Curl_alpn2alpnid(const unsigned char *name, size_t len) if(!memcmp(name, "h3", 2)) return ALPN_h3; } - else if(len == 8) { - if(!memcmp(name, "http/1.1", 8)) - return ALPN_h1; - } + else if(len == 8 && !memcmp(name, "http/1.1", 8)) + return ALPN_h1; return ALPN_none; /* unknown, probably rubbish input */ } diff --git a/lib/cshutdn.c b/lib/cshutdn.c index 10cafdd065e3..e9ce1f6e7b8a 100644 --- a/lib/cshutdn.c +++ b/lib/cshutdn.c @@ -403,13 +403,11 @@ void Curl_cshutdn_add(struct cshutdn *cshutdn, cshutdn_destroy_oldest(cshutdn, NULL); } - if(cshutdn->multi->socket_cb) { - if(cshutdn_update_ev(cshutdn, conn)) { - CURL_TRC_M(admin, "[SHUTDOWN] update events failed, discarding #%" - FMT_OFF_T, conn->connection_id); - Curl_conn_terminate(admin, conn, FALSE); - return; - } + if(cshutdn->multi->socket_cb && cshutdn_update_ev(cshutdn, conn)) { + CURL_TRC_M(admin, "[SHUTDOWN] update events failed, discarding #%" + FMT_OFF_T, conn->connection_id); + Curl_conn_terminate(admin, conn, FALSE); + return; } Curl_llist_append(&cshutdn->list, conn, &conn->cshutdn_node); diff --git a/lib/curl_share.c b/lib/curl_share.c index cbadd57d3795..d27c6fe16861 100644 --- a/lib/curl_share.c +++ b/lib/curl_share.c @@ -379,10 +379,9 @@ CURLSHcode Curl_share_lock_share(struct Curl_share *share, if(!share) return CURLSHE_INVALID; - if(share->specifier & (unsigned int)(1 << type)) { - if(share->lockfunc) /* only call this if set! */ - share->lockfunc(data, type, accesstype, share->clientdata); - } + if(share->specifier & (unsigned int)(1 << type) && + share->lockfunc) /* only call this if set! */ + share->lockfunc(data, type, accesstype, share->clientdata); /* else if we do not share this, pretend successful lock */ return CURLSHE_OK; @@ -401,10 +400,9 @@ CURLSHcode Curl_share_unlock_share(struct Curl_share *share, if(!share) return CURLSHE_INVALID; - if(share->specifier & (unsigned int)(1 << type)) { - if(share->unlockfunc) /* only call this if set! */ - share->unlockfunc(data, type, share->clientdata); - } + if(share->specifier & (unsigned int)(1 << type) && + share->unlockfunc) /* only call this if set! */ + share->unlockfunc(data, type, share->clientdata); return CURLSHE_OK; } diff --git a/lib/curlx/inet_pton.c b/lib/curlx/inet_pton.c index 23e76725fe68..3b611890f9c7 100644 --- a/lib/curlx/inet_pton.c +++ b/lib/curlx/inet_pton.c @@ -121,9 +121,8 @@ static int inet_pton6(const char *src, unsigned char *dst) endp = tp + IN6ADDRSZ; colonp = NULL; /* Leading :: requires some special handling. */ - if(*src == ':') - if(*++src != ':') - return 0; + if(*src == ':' && *++src != ':') + return 0; curtok = src; saw_xdigit = 0; val = 0; diff --git a/lib/easy.c b/lib/easy.c index 5ebd3abae390..7572e44a2a8d 100644 --- a/lib/easy.c +++ b/lib/easy.c @@ -1234,17 +1234,15 @@ CURLcode curl_easy_pause(CURL *curl, int action) if(data->multi) { Curl_multi_mark_dirty(data); /* make it run */ /* On changes, tell application to update its timers. */ - if(changed) { - if(Curl_update_timer(data->multi) && !result) - result = CURLE_ABORTED_BY_CALLBACK; - } + if(changed && Curl_update_timer(data->multi) && !result) + result = CURLE_ABORTED_BY_CALLBACK; } } - if(!result && changed && !data->state.done && data->multi) - /* pause/unpausing may result in multi event changes */ - if(Curl_multi_ev_assess_xfer(data->multi, data) && !result) - result = CURLE_ABORTED_BY_CALLBACK; + if(!result && changed && !data->state.done && data->multi && + /* pause/unpausing may result in multi event changes */ + Curl_multi_ev_assess_xfer(data->multi, data)) + result = CURLE_ABORTED_BY_CALLBACK; } out: CURL_EAPI_LEAVE(&guard); diff --git a/lib/formdata.c b/lib/formdata.c index 0c81d8c781c9..627744f19aa4 100644 --- a/lib/formdata.c +++ b/lib/formdata.c @@ -256,22 +256,18 @@ static CURLFORMcode formadd_check(struct FormInfo *first_form, if(Curl_bufref_memdup0(&form->contenttype, type, strlen(type))) return CURL_FORMADD_MEMORY; } - if(name && form->namelength) { - if(memchr(name, 0, form->namelength)) - return CURL_FORMADD_NULL; - } - if(!(form->flags & HTTPPOST_PTRNAME)) { - /* Note that there is small risk that form->name is NULL here if the app - passed in a bad combo, so we check for that. */ - if(forminfo_copyfield(&form->name, form->namelength)) - return CURL_FORMADD_MEMORY; - } + if(name && form->namelength && memchr(name, 0, form->namelength)) + return CURL_FORMADD_NULL; + if(!(form->flags & HTTPPOST_PTRNAME) && + /* Note that there is small risk that form->name is NULL here if the app + passed in a bad combo, so we check for that. */ + forminfo_copyfield(&form->name, form->namelength)) + return CURL_FORMADD_MEMORY; if(!(form->flags & (HTTPPOST_FILENAME | HTTPPOST_READFILE | HTTPPOST_PTRCONTENTS | HTTPPOST_PTRBUFFER | - HTTPPOST_CALLBACK))) { - if(forminfo_copyfield(&form->value, (size_t)form->contentslength)) - return CURL_FORMADD_MEMORY; - } + HTTPPOST_CALLBACK)) && + forminfo_copyfield(&form->value, (size_t)form->contentslength)) + return CURL_FORMADD_MEMORY; post = httppost_add(form, post, httppost, last_post); if(!post) @@ -826,10 +822,10 @@ CURLcode Curl_getformdata(CURL *data, } /* Set fake filename. */ - if(!result && post->showfilename) - if(post->more || (post->flags & (HTTPPOST_FILENAME | HTTPPOST_BUFFER | - HTTPPOST_CALLBACK))) - result = curl_mime_filename(part, post->showfilename); + if(!result && post->showfilename && + (post->more || (post->flags & (HTTPPOST_FILENAME | HTTPPOST_BUFFER | + HTTPPOST_CALLBACK)))) + result = curl_mime_filename(part, post->showfilename); } } diff --git a/lib/ftp.c b/lib/ftp.c index 70bf654bf914..f3cb75f2e3cb 100644 --- a/lib/ftp.c +++ b/lib/ftp.c @@ -2673,13 +2673,12 @@ static CURLcode ftp_state_size_resp(struct Curl_easy *data, if(curlx_str_number(&fdigit, &filesize, CURL_OFF_T_MAX)) filesize = -1; /* size remain unknown */ } - else if(ftpcode == 550) { /* "No such file or directory" */ - /* allow a SIZE failure for (resumed) uploads, when probing what command - to use */ - if(instate != FTP_STOR_SIZE) { - failf(data, "The file does not exist"); - return CURLE_REMOTE_FILE_NOT_FOUND; - } + else if(ftpcode == 550 && /* "No such file or directory" */ + /* allow a SIZE failure for (resumed) uploads, when probing what + command to use */ + instate != FTP_STOR_SIZE) { + failf(data, "The file does not exist"); + return CURLE_REMOTE_FILE_NOT_FOUND; } if(instate == FTP_SIZE) { diff --git a/lib/http.c b/lib/http.c index 4b79b2ab9399..ef0bd39ed5ad 100644 --- a/lib/http.c +++ b/lib/http.c @@ -616,19 +616,18 @@ CURLcode Curl_http_auth_act(struct Curl_easy *data) } else if((data->req.httpcode < 300) && !data->state.authhost.done && - data->req.authneg) { - /* no (known) authentication available, - authentication is not "done" yet and - no authentication seems to be required and - we did not try HEAD or GET */ - if((data->state.httpreq != HTTPREQ_GET) && - (data->state.httpreq != HTTPREQ_HEAD)) { - /* clone URL */ - data->req.newurl = Curl_bufref_dup(&data->state.url); - if(!data->req.newurl) - return CURLE_OUT_OF_MEMORY; - data->state.authhost.done = TRUE; - } + data->req.authneg && + /* no (known) authentication available, + authentication is not "done" yet and + no authentication seems to be required and + we did not try HEAD or GET */ + (data->state.httpreq != HTTPREQ_GET) && + (data->state.httpreq != HTTPREQ_HEAD)) { + /* clone URL */ + data->req.newurl = Curl_bufref_dup(&data->state.url); + if(!data->req.newurl) + return CURLE_OUT_OF_MEMORY; + data->state.authhost.done = TRUE; } if(http_should_fail(data, data->req.httpcode)) { failf(data, "The requested URL returned error: %d", @@ -2547,13 +2546,12 @@ static CURLcode http_add_content_hds(struct Curl_easy *data, } } #endif - if(httpreq == HTTPREQ_POST) { - if(!Curl_checkheaders(data, STRCONST("Content-Type"))) { - result = curlx_dyn_addn(r, STRCONST("Content-Type: application/" - "x-www-form-urlencoded\r\n")); - if(result) - goto out; - } + if(httpreq == HTTPREQ_POST && + !Curl_checkheaders(data, STRCONST("Content-Type"))) { + result = curlx_dyn_addn(r, STRCONST("Content-Type: application/" + "x-www-form-urlencoded\r\n")); + if(result) + goto out; } result = addexpect(data, r, httpversion, &announced_exp100); if(result) @@ -2747,22 +2745,20 @@ static CURLcode http_firstwrite(struct Curl_easy *data) return CURLE_RANGE_ERROR; } - if(data->set.timecondition && !data->state.range) { - /* A time condition has been set AND no ranges have been requested. This - seems to be what chapter 13.3.4 of RFC 2616 defines to be the correct - action for an HTTP/1.1 client */ - - if(!Curl_meets_timecondition(data, k->timeofdoc)) { - k->done = TRUE; - /* We are simulating an HTTP 304 from server so we return - what should have been returned from the server */ - data->info.httpcode = 304; - infof(data, "Simulate an HTTP 304 response"); - /* we abort the transfer before it is completed == we ruin the - reuse ability. Close the connection */ - streamclose(conn); - return CURLE_OK; - } + if(data->set.timecondition && !data->state.range && + /* A time condition has been set AND no ranges have been requested. This + seems to be what chapter 13.3.4 of RFC 2616 defines to be the correct + action for an HTTP/1.1 client */ + !Curl_meets_timecondition(data, k->timeofdoc)) { + k->done = TRUE; + /* We are simulating an HTTP 304 from server so we return + what should have been returned from the server */ + data->info.httpcode = 304; + infof(data, "Simulate an HTTP 304 response"); + /* we abort the transfer before it is completed == we ruin the + reuse ability. Close the connection */ + streamclose(conn); + return CURLE_OK; } /* we have a time condition */ return CURLE_OK; diff --git a/lib/http_aws_sigv4.c b/lib/http_aws_sigv4.c index b16c2cd2aaa9..d46a6a10cfa7 100644 --- a/lib/http_aws_sigv4.c +++ b/lib/http_aws_sigv4.c @@ -186,10 +186,8 @@ static CURLcode split_to_dyn_array(const char *source, if(segment_length) { curlx_dyn_init(&db[index], segment_length + 1); result = curlx_dyn_addn(&db[index], &source[start], segment_length); - if(!result) { - if(++num_splits == MAX_QUERY_COMPONENTS) - result = CURLE_TOO_LARGE; - } + if(!result && ++num_splits == MAX_QUERY_COMPONENTS) + result = CURLE_TOO_LARGE; } fail: *num_splits_out = num_splits; @@ -523,10 +521,8 @@ static CURLcode make_headers(struct Curl_easy *data, if(tmp) *tmp = 0; - if(l != head) { - if(curlx_dyn_add(signed_headers, ";")) - goto fail; - } + if(l != head && curlx_dyn_add(signed_headers, ";")) + goto fail; if(curlx_dyn_add(signed_headers, l->data)) goto fail; } @@ -692,10 +688,8 @@ UNITTEST CURLcode canon_path(const char *q, size_t len, else result = curlx_dyn_addn(new_path, q, len); - if(!result) { - if(curlx_dyn_len(new_path) == 0) - result = curlx_dyn_add(new_path, "/"); - } + if(!result && curlx_dyn_len(new_path) == 0) + result = curlx_dyn_add(new_path, "/"); return result; } diff --git a/lib/imap.c b/lib/imap.c index 62ab0a283bd1..9586e4f6d6eb 100644 --- a/lib/imap.c +++ b/lib/imap.c @@ -877,11 +877,8 @@ static CURLcode imap_perform_append(struct Curl_easy *data, result = Curl_mime_prepare_headers(data, postp, NULL, NULL, MIMESTRATEGY_MAIL); - if(!result) - if(!Curl_checkheaders(data, STRCONST("Mime-Version"))) - result = Curl_mime_add_header(&postp->curlheaders, - "Mime-Version: 1.0"); - + if(!result && !Curl_checkheaders(data, STRCONST("Mime-Version"))) + result = Curl_mime_add_header(&postp->curlheaders, "Mime-Version: 1.0"); if(!result) result = Curl_creader_set_mime(data, postp); if(result) @@ -931,12 +928,11 @@ static CURLcode imap_perform_append(struct Curl_easy *data, } for(i = 0; ulflag[i].bit; i++) { - if(data->set.upload_flags & ulflag[i].bit) { - if((curlx_dyn_len(&flags) > 2 && curlx_dyn_add(&flags, " ")) || - curlx_dyn_add(&flags, "\\") || - curlx_dyn_add(&flags, ulflag[i].flag)) - goto cleanup; - } + if(data->set.upload_flags & ulflag[i].bit && + ((curlx_dyn_len(&flags) > 2 && curlx_dyn_add(&flags, " ")) || + curlx_dyn_add(&flags, "\\") || + curlx_dyn_add(&flags, ulflag[i].flag))) + goto cleanup; } if(curlx_dyn_add(&flags, ")")) @@ -1210,11 +1206,10 @@ static bool is_custom_fetch_listing(struct IMAP *imap) const char *p = imap->custom_params; return is_custom_fetch_listing_match(p); } - else if(curl_strequal(imap->custom, "UID") && imap->custom_params) { - if(curl_strnequal(imap->custom_params, " FETCH ", 7)) { - const char *p = imap->custom_params + 6; - return is_custom_fetch_listing_match(p); - } + else if(curl_strequal(imap->custom, "UID") && imap->custom_params && + curl_strnequal(imap->custom_params, " FETCH ", 7)) { + const char *p = imap->custom_params + 6; + return is_custom_fetch_listing_match(p); } return FALSE; } @@ -2206,17 +2201,16 @@ static CURLcode imap_disconnect(struct Curl_easy *data, { struct imap_conn *imapc = Curl_conn_meta_get(conn, CURL_META_IMAP_CONN); - if(imapc) { - /* We cannot send quit unconditionally. If this connection is stale or - bad in any way (pingpong has pending data to send), - sending quit and waiting around here will make the - disconnect wait in vain and cause more problems than we need to. */ - if(!dead_connection && conn->bits.protoconnstart && - !Curl_pp_needs_flush(data, &imapc->pp)) { - if(!imap_perform_logout(data, imapc)) - (void)imap_block_statemach(data, imapc, TRUE); /* ignore errors */ - } - } + if(imapc && + /* We cannot send quit unconditionally. If this connection is stale or + bad in any way (pingpong has pending data to send), + sending quit and waiting around here will make the + disconnect wait in vain and cause more problems than we need to. */ + !dead_connection && conn->bits.protoconnstart && + !Curl_pp_needs_flush(data, &imapc->pp) && + !imap_perform_logout(data, imapc)) + (void)imap_block_statemach(data, imapc, TRUE); /* ignore errors */ + return CURLE_OK; } diff --git a/lib/mime.c b/lib/mime.c index 600437576581..0e23259e6625 100644 --- a/lib/mime.c +++ b/lib/mime.c @@ -1706,13 +1706,15 @@ static CURLcode add_content_disposition(struct Curl_easy *data, const char *contenttype, enum mimestrategy strategy) { - if(!disposition) - if(part->filename || part->name || - (contenttype && !curl_strnequal(contenttype, "multipart/", 10))) - disposition = DISPOSITION_DEFAULT; + if(!disposition && + (part->filename || part->name || + (contenttype && !curl_strnequal(contenttype, "multipart/", 10)))) + disposition = DISPOSITION_DEFAULT; + if(disposition && curl_strequal(disposition, "attachment") && !part->name && !part->filename) disposition = NULL; + if(disposition) { CURLcode result = CURLE_OK; char *name = NULL; @@ -1808,9 +1810,9 @@ CURLcode Curl_mime_prepare_headers(struct Curl_easy *data, boundary = mime->boundary; } else if(contenttype && !customct && - content_type_match(contenttype, STRCONST("text/plain"))) - if(strategy == MIMESTRATEGY_MAIL || !part->filename) - contenttype = NULL; + content_type_match(contenttype, STRCONST("text/plain")) && + (strategy == MIMESTRATEGY_MAIL || !part->filename)) + contenttype = NULL; /* Issue content-disposition header only if not already set by caller. */ if(!search_header(part->userheaders, STRCONST("Content-Disposition"))) { diff --git a/lib/multi.c b/lib/multi.c index ce303b40f316..dc0743456f52 100644 --- a/lib/multi.c +++ b/lib/multi.c @@ -1401,11 +1401,9 @@ static CURLMcode multi_winsock_select(struct Curl_multi *multi, mask |= FD_WRITE | FD_CONNECT | FD_CLOSE; reset_socket_fdwrite(cpfds->pfds[i].fd); } - if(mask) { - if(WSAEventSelect(cpfds->pfds[i].fd, multi->wsa_event, mask)) { - mresult = CURLM_OUT_OF_MEMORY; - goto out; - } + if(mask && WSAEventSelect(cpfds->pfds[i].fd, multi->wsa_event, mask)) { + mresult = CURLM_OUT_OF_MEMORY; + goto out; } } diff --git a/lib/pop3.c b/lib/pop3.c index b32663ce1473..16b742cc6c54 100644 --- a/lib/pop3.c +++ b/lib/pop3.c @@ -697,9 +697,8 @@ static CURLcode pop3_perform_authentication(struct Curl_easy *data, /* Calculate the SASL login details */ result = Curl_sasl_start(&pop3c->sasl, data, FALSE, &progress); - if(!result) - if(progress == SASL_INPROGRESS) - pop3_state(data, POP3_AUTH); + if(!result && progress == SASL_INPROGRESS) + pop3_state(data, POP3_AUTH); } if(!result && progress == SASL_IDLE) { @@ -1622,10 +1621,9 @@ static CURLcode pop3_disconnect(struct Curl_easy *data, disconnect wait in vain and cause more problems than we need to. */ if(!dead_connection && conn->bits.protoconnstart && - !Curl_pp_needs_flush(data, &pop3c->pp)) { - if(!pop3_perform_quit(data, conn)) - (void)pop3_block_statemach(data, conn, TRUE); /* ignore errors on QUIT */ - } + !Curl_pp_needs_flush(data, &pop3c->pp) && + !pop3_perform_quit(data, conn)) + (void)pop3_block_statemach(data, conn, TRUE); /* ignore errors on QUIT */ /* Disconnect from the server */ Curl_pp_disconnect(&pop3c->pp); diff --git a/lib/rtsp.c b/lib/rtsp.c index fabdda3d3eed..736f8aeabc1b 100644 --- a/lib/rtsp.c +++ b/lib/rtsp.c @@ -410,16 +410,14 @@ static CURLcode rtsp_setup_request(struct Curl_easy *data, data->state.use_range && ((rtspreq == RTSPREQ_PLAY) || (rtspreq == RTSPREQ_PAUSE) || - (rtspreq == RTSPREQ_RECORD))) { - - /* Check to see if there is a range set in the custom headers */ - if(!Curl_checkheaders(data, STRCONST("Range")) && data->state.range) { - result = rtsp_header_alloc("Range", - data->state.range, - &data->state.rangeline); - if(!result) - b->range = data->state.rangeline; - } + (rtspreq == RTSPREQ_RECORD)) && + /* Check to see if there is a range set in the custom headers */ + !Curl_checkheaders(data, STRCONST("Range")) && data->state.range) { + result = rtsp_header_alloc("Range", + data->state.range, + &data->state.rangeline); + if(!result) + b->range = data->state.rangeline; } return result; } @@ -935,12 +933,10 @@ static CURLcode rtsp_parse_transport(struct Curl_easy *data, if(!curlx_str_number(&p, &chan1, 255)) { unsigned char *rtp_channel_mask = data->state.rtp_channel_mask; chan2 = chan1; - if(!curlx_str_single(&p, '-')) { - if(curlx_str_number(&p, &chan2, 255)) { - infof(data, "Unable to read the interleaved parameter from " - "Transport header: [%s]", transport); - chan2 = chan1; - } + if(!curlx_str_single(&p, '-') && curlx_str_number(&p, &chan2, 255)) { + infof(data, "Unable to read the interleaved parameter from " + "Transport header: [%s]", transport); + chan2 = chan1; } for(chan = chan1; chan <= chan2; chan++) { int idx = (int)chan / 8; diff --git a/lib/select.c b/lib/select.c index 76b56bc0426b..f6ab6a6d4e90 100644 --- a/lib/select.c +++ b/lib/select.c @@ -385,10 +385,8 @@ static CURLcode cpfds_add_sock(struct curl_pollfds *cpfds, } } /* not folded, add new entry */ - if(cpfds->n >= cpfds->count) { - if(cpfds_increase(cpfds, 100)) - return CURLE_OUT_OF_MEMORY; - } + if(cpfds->n >= cpfds->count && cpfds_increase(cpfds, 100)) + return CURLE_OUT_OF_MEMORY; cpfds->pfds[cpfds->n].fd = sock; cpfds->pfds[cpfds->n].events = events; ++cpfds->n; @@ -414,10 +412,8 @@ CURLcode Curl_pollfds_add_ps(struct curl_pollfds *cpfds, events |= POLLIN; if(ps->actions[i] & CURL_POLL_OUT) events |= POLLOUT; - if(events) { - if(cpfds_add_sock(cpfds, ps->sockets[i], events, TRUE)) - return CURLE_OUT_OF_MEMORY; - } + if(events && cpfds_add_sock(cpfds, ps->sockets[i], events, TRUE)) + return CURLE_OUT_OF_MEMORY; } return CURLE_OK; } diff --git a/lib/smtp.c b/lib/smtp.c index 50e2af400a10..75fc5b7ece4b 100644 --- a/lib/smtp.c +++ b/lib/smtp.c @@ -274,10 +274,8 @@ static CURLcode smtp_parse_address(struct Curl_easy *data, const char *fqma, if(fqma[0] != '<') { length = strlen(dup); - if(length) { - if(dup[length - 1] == '>') - dup[length - 1] = '\0'; - } + if(length && dup[length - 1] == '>') + dup[length - 1] = '\0'; } else { addressend = strrchr(dup, '>'); @@ -1018,11 +1016,8 @@ static CURLcode smtp_perform_mail(struct Curl_easy *data, result = Curl_mime_prepare_headers(data, postp, NULL, NULL, MIMESTRATEGY_MAIL); - if(!result) - if(!Curl_checkheaders(data, STRCONST("Mime-Version"))) - result = Curl_mime_add_header(&postp->curlheaders, - "Mime-Version: 1.0"); - + if(!result && !Curl_checkheaders(data, STRCONST("Mime-Version"))) + result = Curl_mime_add_header(&postp->curlheaders, "Mime-Version: 1.0"); if(!result) result = Curl_creader_set_mime(data, postp); if(result) @@ -1922,10 +1917,9 @@ static CURLcode smtp_disconnect(struct Curl_easy *data, disconnect wait in vain and cause more problems than we need to. */ if(!dead_connection && conn->bits.protoconnstart && - !Curl_pp_needs_flush(data, &smtpc->pp)) { - if(!smtp_perform_quit(data, smtpc)) - (void)smtp_block_statemach(data, smtpc, TRUE); /* ignore on QUIT */ - } + !Curl_pp_needs_flush(data, &smtpc->pp) && + !smtp_perform_quit(data, smtpc)) + (void)smtp_block_statemach(data, smtpc, TRUE); /* ignore on QUIT */ CURL_TRC_SMTP(data, "smtp_disconnect(), finished"); return CURLE_OK; diff --git a/lib/telnet.c b/lib/telnet.c index 735fa66156fd..78afc5193d75 100644 --- a/lib/telnet.c +++ b/lib/telnet.c @@ -1414,13 +1414,12 @@ static CURLcode telnet_do(struct Curl_easy *data, bool *done) } } /* switch */ - if(data->set.timeout) { - if(curlx_ptimediff_ms(Curl_pgrs_now(data), &conn->created) >= - data->set.timeout) { - failf(data, "Time-out"); - result = CURLE_OPERATION_TIMEDOUT; - keepon = FALSE; - } + if(data->set.timeout && + curlx_ptimediff_ms(Curl_pgrs_now(data), &conn->created) >= + data->set.timeout) { + failf(data, "Time-out"); + result = CURLE_OPERATION_TIMEDOUT; + keepon = FALSE; } } @@ -1533,13 +1532,12 @@ static CURLcode telnet_do(struct Curl_easy *data, bool *done) break; } /* poll switch statement */ - if(data->set.timeout) { - if(curlx_ptimediff_ms(Curl_pgrs_now(data), &conn->created) >= - data->set.timeout) { - failf(data, "Time-out"); - result = CURLE_OPERATION_TIMEDOUT; - keepon = FALSE; - } + if(data->set.timeout && + curlx_ptimediff_ms(Curl_pgrs_now(data), &conn->created) >= + data->set.timeout) { + failf(data, "Time-out"); + result = CURLE_OPERATION_TIMEDOUT; + keepon = FALSE; } if(!result) { diff --git a/lib/url.c b/lib/url.c index 6eeae43d0d42..00b77be1649f 100644 --- a/lib/url.c +++ b/lib/url.c @@ -611,7 +611,7 @@ static bool url_match_connect_config(struct connectdata *conn, m->data->set.ipver != conn->ip_version) return FALSE; - if(m->needle->localdev || m->needle->localport) { + if((m->needle->localdev || m->needle->localport) && /* If we are bound to a specific local end (IP+port), we must not reuse a random other one, although if we did not ask for a particular one we can reuse one that was bound. @@ -622,12 +622,11 @@ static bool url_match_connect_config(struct connectdata *conn, this matching will assume that reuses of bound connections will most likely also reuse the exact same binding parameters and missing out a few edge cases should not hurt anyone much. */ - if((conn->localport != m->needle->localport) || - (conn->localportrange != m->needle->localportrange) || - (m->needle->localdev && - (!conn->localdev || strcmp(conn->localdev, m->needle->localdev)))) - return FALSE; - } + ((conn->localport != m->needle->localport) || + (conn->localportrange != m->needle->localportrange) || + (m->needle->localdev && + (!conn->localdev || strcmp(conn->localdev, m->needle->localdev))))) + return FALSE; if(!m->needle->via_peer != !conn->via_peer) /* do not mix connections that use the "connect to host" feature and @@ -869,16 +868,15 @@ static bool url_match_destination(struct connectdata *conn, if(!Curl_peer_same_destination(m->needle->via_peer, conn->via_peer)) return FALSE; - if(m->needle->origin->scheme != conn->origin->scheme) { + if(m->needle->origin->scheme != conn->origin->scheme && /* `needle` and `conn` not having the same scheme. * This is allowed for the same family *if* conn is using TLS. * - IMAP+STARTTLS works for IMAPS. * - IMAPS works for IMAP. */ - if(get_protocol_family(conn->origin->scheme) != - m->needle->scheme->protocol) { - return FALSE; - } - } + get_protocol_family(conn->origin->scheme) != + m->needle->scheme->protocol) + return FALSE; + /* Scheme mismatch is acceptable, compare hostname/port */ return Curl_peer_same_destination(m->needle->origin, conn->origin); } @@ -1382,15 +1380,14 @@ static CURLcode url_set_data_creds_netrc(struct Curl_easy *data, goto out; } else if(ncreds_out) { - if(!(data->state.origin->scheme->flags & PROTOPT_USERPWDCTRL)) { - /* if the protocol cannot handle control codes in credentials, make - sure there are none */ - if(str_has_ctrl(ncreds_out->user) || - str_has_ctrl(ncreds_out->passwd)) { - failf(data, "control code detected in .netrc credentials"); - result = CURLE_READ_ERROR; - goto out; - } + if(!(data->state.origin->scheme->flags & PROTOPT_USERPWDCTRL) && + /* if the protocol cannot handle control codes in credentials, make + sure there are none */ + (str_has_ctrl(ncreds_out->user) || + str_has_ctrl(ncreds_out->passwd))) { + failf(data, "control code detected in .netrc credentials"); + result = CURLE_READ_ERROR; + goto out; } CURL_TRC_M(data, "netrc: using credentials for %s as %s", data->state.origin->hostname, ncreds_out->user); diff --git a/lib/urlapi.c b/lib/urlapi.c index 734efbc35e69..c7bb3539de24 100644 --- a/lib/urlapi.c +++ b/lib/urlapi.c @@ -1479,15 +1479,13 @@ static CURLUcode urlget_format(const CURLU *u, CURLUPart what, part = punyversion; } } - else if(depunyfy) { - if(Curl_is_ASCII_name(u->host)) { - char *unpunified = NULL; - uc = host_encode(part, &unpunified); - curlx_free(part); - if(uc) - return uc; - part = unpunified; - } + else if(depunyfy && Curl_is_ASCII_name(u->host)) { + char *unpunified = NULL; + uc = host_encode(part, &unpunified); + curlx_free(part); + if(uc) + return uc; + part = unpunified; } *partp = part; return CURLUE_OK; diff --git a/lib/vtls/openssl.c b/lib/vtls/openssl.c index dedd8f3e70bd..fed1af1bede5 100644 --- a/lib/vtls/openssl.c +++ b/lib/vtls/openssl.c @@ -1026,13 +1026,9 @@ static int enginecheck(struct Curl_easy *data, /* Implicitly use pkcs11 engine if none was provided and the * key_file is a PKCS#11 URI */ - if(!data->state.engine) { - if(is_pkcs11_uri(key_file)) { - if(ossl_set_engine(data, "pkcs11") != CURLE_OK) { - return 0; - } - } - } + if(!data->state.engine && is_pkcs11_uri(key_file) && + ossl_set_engine(data, "pkcs11") != CURLE_OK) + return 0; if(data->state.engine) { UI_METHOD *ui_method = UI_create_method("curl user interface"); @@ -1081,12 +1077,9 @@ static int providercheck(struct Curl_easy *data, char error_buffer[256]; /* Implicitly use pkcs11 provider if none was provided and the * key_file is a PKCS#11 URI */ - if(!data->state.provider_loaded) { - if(is_pkcs11_uri(key_file)) { - if(ossl_set_provider(data, "pkcs11") != CURLE_OK) { - return 0; - } - } + if(!data->state.provider_loaded && is_pkcs11_uri(key_file) && + ossl_set_provider(data, "pkcs11") != CURLE_OK) { + return 0; } if(data->state.provider_loaded) { @@ -1168,13 +1161,9 @@ static int engineload(struct Curl_easy *data, char error_buffer[256]; /* Implicitly use pkcs11 engine if none was provided and the * cert_file is a PKCS#11 URI */ - if(!data->state.engine) { - if(is_pkcs11_uri(cert_file)) { - if(ossl_set_engine(data, "pkcs11") != CURLE_OK) { - return 0; - } - } - } + if(!data->state.engine && is_pkcs11_uri(cert_file) && + ossl_set_engine(data, "pkcs11") != CURLE_OK) + return 0; if(data->state.engine) { static const char cmd_name[] = "LOAD_CERT_CTRL"; @@ -1237,13 +1226,9 @@ static int providerload(struct Curl_easy *data, char error_buffer[256]; /* Implicitly use pkcs11 provider if none was provided and the * cert_file is a PKCS#11 URI */ - if(!data->state.provider_loaded) { - if(is_pkcs11_uri(cert_file)) { - if(ossl_set_provider(data, "pkcs11") != CURLE_OK) { - return 0; - } - } - } + if(!data->state.provider_loaded && is_pkcs11_uri(cert_file) && + ossl_set_provider(data, "pkcs11") != CURLE_OK) + return 0; if(data->state.provider_loaded) { /* Load the certificate from the provider */ diff --git a/src/config2setopts.c b/src/config2setopts.c index b6c242dad6cb..2bb859ac6830 100644 --- a/src/config2setopts.c +++ b/src/config2setopts.c @@ -261,9 +261,9 @@ static long tlsversion(unsigned char mintls, unsigned char maxtls) { long tlsver = 0; - if(!mintls) { /* minimum is at default */ - /* minimum is set to default, which we want to be 1.2 */ - if(maxtls && (maxtls < 3)) + if(!mintls && /* minimum is at default */ + /* minimum is set to default, which we want to be 1.2 */ + maxtls && (maxtls < 3)) { /* max is set lower than 1.2 and minimum is default, change minimum to the same as max */ mintls = maxtls; diff --git a/src/tool_cb_dbg.c b/src/tool_cb_dbg.c index 2a69ce0f1179..ea2ec964b08d 100644 --- a/src/tool_cb_dbg.c +++ b/src/tool_cb_dbg.c @@ -225,18 +225,17 @@ int tool_debug_cb(CURL *handle, curl_infotype type, case CURLINFO_DATA_IN: case CURLINFO_SSL_DATA_IN: case CURLINFO_SSL_DATA_OUT: - if(!traced_data) { - /* if the data is output to a tty and we are sending this debug trace - to stderr or stdout, we do not display the alert about the data not - being shown as the data _is_ shown then not via this function */ - if(!global->isatty || - ((output != tool_stderr) && (output != stdout))) { - if(!newl) - log_line_start(output, timebuf, idsbuf, type); - curl_mfprintf(output, "[%zu bytes data]\n", size); - newl = FALSE; - traced_data = TRUE; - } + if(!traced_data && + /* if the data is output to a tty and we are sending this debug trace + to stderr or stdout, we do not display the alert about the data not + being shown as the data _is_ shown then not via this function */ + (!global->isatty || + ((output != tool_stderr) && (output != stdout)))) { + if(!newl) + log_line_start(output, timebuf, idsbuf, type); + curl_mfprintf(output, "[%zu bytes data]\n", size); + newl = FALSE; + traced_data = TRUE; } break; default: /* nada */ diff --git a/src/tool_cb_wrt.c b/src/tool_cb_wrt.c index ffaf0b76db3c..211fb521a4f7 100644 --- a/src/tool_cb_wrt.c +++ b/src/tool_cb_wrt.c @@ -347,10 +347,9 @@ size_t tool_write_cb(char *buffer, size_t sz, size_t nmemb, void *userdata) else #endif { - if(per->hdrcbdata.headlist) { - if(tool_write_headers(&per->hdrcbdata, outs->stream)) - return CURL_WRITEFUNC_ERROR; - } + if(per->hdrcbdata.headlist && + tool_write_headers(&per->hdrcbdata, outs->stream)) + return CURL_WRITEFUNC_ERROR; rc = fwrite(buffer, sz, nmemb, outs->stream); } diff --git a/src/tool_formparse.c b/src/tool_formparse.c index 450a2b3a994a..eea511d25056 100644 --- a/src/tool_formparse.c +++ b/src/tool_formparse.c @@ -240,10 +240,8 @@ static int tool_mime_stdin_seek(void *instream, curl_off_t offset, int whence) } if(offset < 0) return CURL_SEEKFUNC_CANTSEEK; - if(!sip->data) { - if(curlx_fseek(stdin, offset + sip->origin, SEEK_SET)) - return CURL_SEEKFUNC_CANTSEEK; - } + if(!sip->data && curlx_fseek(stdin, offset + sip->origin, SEEK_SET)) + return CURL_SEEKFUNC_CANTSEEK; sip->curpos = offset; return CURL_SEEKFUNC_OK; } @@ -382,15 +380,13 @@ static char *get_param_word(char **str, char **end_pos, char endchar) if(*ptr == '"') { ++ptr; while(*ptr) { - if(*ptr == '\\') { - if(ptr[1] == '\\' || ptr[1] == '"') { - /* remember the first escape position */ - if(!escape) - escape = ptr; - /* skip escape of back-slash or double-quote */ - ptr += 2; - continue; - } + if(*ptr == '\\' && (ptr[1] == '\\' || ptr[1] == '"')) { + /* remember the first escape position */ + if(!escape) + escape = ptr; + /* skip escape of back-slash or double-quote */ + ptr += 2; + continue; } if(*ptr == '"') { bool trailing_data = FALSE; diff --git a/src/tool_getparam.c b/src/tool_getparam.c index ae3958d8c58f..4694bf3c90bc 100644 --- a/src/tool_getparam.c +++ b/src/tool_getparam.c @@ -1001,12 +1001,10 @@ static ParameterError set_data(cmdline_t cmd, if(cmd == C_JSON) config->jsoned = TRUE; - if(curlx_dyn_len(&config->postdata)) { - /* skip separator append for --json */ - if(!err && (cmd != C_JSON) && - curlx_dyn_addn(&config->postdata, "&", 1)) - err = PARAM_NO_MEM; - } + /* skip separator append for --json */ + if(curlx_dyn_len(&config->postdata) && !err && (cmd != C_JSON) && + curlx_dyn_addn(&config->postdata, "&", 1)) + err = PARAM_NO_MEM; if(!err && curlx_dyn_addn(&config->postdata, postdata, size)) err = PARAM_NO_MEM; @@ -2812,10 +2810,8 @@ static ParameterError opt_string(struct OperationConfig *config, break; case C_HOSTPUBMD5: /* --hostpubmd5 */ err = getstr(&config->hostpubmd5, nextarg, DENY_BLANK); - if(!err) { - if(!config->hostpubmd5 || strlen(config->hostpubmd5) != 32) - err = PARAM_BAD_USE; - } + if(!err && (!config->hostpubmd5 || strlen(config->hostpubmd5) != 32)) + err = PARAM_BAD_USE; break; case C_HOSTPUBSHA256: /* --hostpubsha256 */ err = getstr(&config->hostpubsha256, nextarg, DENY_BLANK); @@ -3189,10 +3185,8 @@ ParameterError parse_args(int argc, argv_item_t argv[]) } } - if(!err && config->content_disposition) { - if(config->resume_from_current) - err = PARAM_CONTDISP_RESUME_FROM; - } + if(!err && config->content_disposition && config->resume_from_current) + err = PARAM_CONTDISP_RESUME_FROM; if(err && err != PARAM_HELP_REQUESTED && diff --git a/src/tool_urlglob.c b/src/tool_urlglob.c index 88deedcb4cfd..836d6431205d 100644 --- a/src/tool_urlglob.c +++ b/src/tool_urlglob.c @@ -669,10 +669,9 @@ CURLcode glob_next_url(char **globbed, struct URLGlob *glob) pat = &glob->pattern[i]; switch(pat->type) { case GLOB_SET: - if(pat->c.set.elem) { - if(curlx_dyn_add(&glob->buf, pat->c.set.elem[pat->c.set.idx])) - return CURLE_OUT_OF_MEMORY; - } + if(pat->c.set.elem && + curlx_dyn_add(&glob->buf, pat->c.set.elem[pat->c.set.idx])) + return CURLE_OUT_OF_MEMORY; break; case GLOB_ASCII: { char letter = (char)pat->c.ascii.letter; diff --git a/src/tool_writeout.c b/src/tool_writeout.c index 7a0a20a9d9fc..fc346353ff65 100644 --- a/src/tool_writeout.c +++ b/src/tool_writeout.c @@ -228,12 +228,10 @@ static int writeString(FILE *stream, const struct writeoutvar *wovar, len = curlx_dyn_len(&buf); if(len) { char *ptr = curlx_dyn_ptr(&buf); - if(ptr[len - 1] != '\n') { - /* add a newline to make things look better */ - if(curlx_dyn_addn(&buf, "\n", 1)) { - error = TRUE; - break; - } + /* add a newline to make things look better */ + if(ptr[len - 1] != '\n' && curlx_dyn_addn(&buf, "\n", 1)) { + error = TRUE; + break; } } } @@ -288,11 +286,9 @@ static int writeString(FILE *stream, const struct writeoutvar *wovar, case VAR_INPUT_URLEQUERY: case VAR_INPUT_URLEFRAGMENT: case VAR_INPUT_URLEZONEID: - if(per->url) { - if(!urlpart(per, wovar->id, &strinfo)) { - freestr = strinfo; - valid = TRUE; - } + if(per->url && !urlpart(per, wovar->id, &strinfo)) { + freestr = strinfo; + valid = TRUE; } break; default: @@ -678,13 +674,11 @@ static void output_header(struct per_transfer *per, size_t seplen = 0; size_t vlen = end - ptr; instr = memchr(ptr, ':', vlen); - if(instr) { - /* instructions follow */ - if(!strncmp(&instr[1], "all:", 4)) { - sep = &instr[5]; - seplen = end - sep; - vlen -= (seplen + 5); - } + /* instructions follow */ + if(instr && !strncmp(&instr[1], "all:", 4)) { + sep = &instr[5]; + seplen = end - sep; + vlen -= (seplen + 5); } if(vlen < sizeof(hname)) { memcpy(hname, ptr, vlen); diff --git a/src/var.c b/src/var.c index 464b0c1c2cec..1b6494709f05 100644 --- a/src/var.c +++ b/src/var.c @@ -113,11 +113,9 @@ static ParameterError varfunc(char *c, /* content */ else if(FUNCMATCH(f, FUNC_JSON, FUNC_JSON_LEN)) { f += FUNC_JSON_LEN; curlx_dyn_reset(out); - if(clen) { - if(jsonquoted(c, clen, out, FALSE)) { - err = PARAM_NO_MEM; - break; - } + if(clen && jsonquoted(c, clen, out, FALSE)) { + err = PARAM_NO_MEM; + break; } } else if(FUNCMATCH(f, FUNC_URL, FUNC_URL_LEN)) { @@ -423,11 +421,10 @@ ParameterError setvariable(const char *input) if(curlx_str_number(&line, &startoffset, CURL_OFF_T_MAX) || curlx_str_single(&line, '-')) return PARAM_VAR_SYNTAX; - if(curlx_str_single(&line, ']')) { - if(curlx_str_number(&line, &endoffset, CURL_OFF_T_MAX) || - curlx_str_single(&line, ']')) - return PARAM_VAR_SYNTAX; - } + if(curlx_str_single(&line, ']') && + (curlx_str_number(&line, &endoffset, CURL_OFF_T_MAX) || + curlx_str_single(&line, ']'))) + return PARAM_VAR_SYNTAX; if(startoffset > endoffset) return PARAM_VAR_SYNTAX; } diff --git a/tests/libtest/cli_h2_pausing.c b/tests/libtest/cli_h2_pausing.c index 19a2760245dc..3663d841f022 100644 --- a/tests/libtest/cli_h2_pausing.c +++ b/tests/libtest/cli_h2_pausing.c @@ -260,15 +260,14 @@ static CURLcode test_cli_h2_pausing(const char *URL) while((msg = curl_multi_info_read(multi, &msgs_left)) != NULL) { if(msg->msg == CURLMSG_DONE) { for(i = 0; i < CURL_ARRAYSIZE(handles); i++) { - if(msg->easy_handle == handles[i].curl) { - if(handles[i].paused != 1 || !handles[i].resumed) { - curl_mfprintf(stderr, "ERROR: [%zu] done, paused=%d, " - "resumed=%d, result %d - wtf?\n", i, - handles[i].paused, - handles[i].resumed, (int)msg->data.result); - result = (CURLcode)1; - goto cleanup; - } + if(msg->easy_handle == handles[i].curl && + (handles[i].paused != 1 || !handles[i].resumed)) { + curl_mfprintf(stderr, "ERROR: [%zu] done, paused=%d, " + "resumed=%d, result %d - wtf?\n", i, + handles[i].paused, + handles[i].resumed, (int)msg->data.result); + result = (CURLcode)1; + goto cleanup; } } } diff --git a/tests/libtest/lib1560.c b/tests/libtest/lib1560.c index e18b2d6508e7..083738898f23 100644 --- a/tests/libtest/lib1560.c +++ b/tests/libtest/lib1560.c @@ -1785,13 +1785,11 @@ static int setget_parts(bool has_utf8) (int)uc, (int)setget_parts_list[i].pcode); error++; } - if(!uc) { - if(checkparts(urlp, - setget_parts_list[i].set, - setget_parts_list[i].out, - setget_parts_list[i].getflags)) - error++; /* add */ - } + if(!uc && checkparts(urlp, + setget_parts_list[i].set, + setget_parts_list[i].out, + setget_parts_list[i].getflags)) + error++; /* add */ } else if(rc != CURLUE_OK) { curl_mfprintf(stderr, "Set parts\nin: %s\nreturned %d (expected %d)\n", diff --git a/tests/libtest/lib3102.c b/tests/libtest/lib3102.c index b3af3981a168..5346b26dc4a3 100644 --- a/tests/libtest/lib3102.c +++ b/tests/libtest/lib3102.c @@ -60,16 +60,15 @@ static bool is_chain_in_order(struct curl_certinfo *cert_info) curl_mprintf(" subject: %s\n", subject); curl_mprintf(" issuer: %s\n", issuer); - if(last_issuer) { - /* If the last certificate's issuer matches the current certificate's - * subject, then the chain is in order */ - if(strcmp(last_issuer, subject)) { - curl_mfprintf(stderr, - "cert %d issuer does not match cert %d subject\n", - cert - 1, cert); - curl_mfprintf(stderr, "certificate chain is not in order\n"); - return FALSE; - } + if(last_issuer && + /* If the last certificate's issuer matches the current certificate's + * subject, then the chain is in order */ + strcmp(last_issuer, subject)) { + curl_mfprintf(stderr, + "cert %d issuer does not match cert %d subject\n", + cert - 1, cert); + curl_mfprintf(stderr, "certificate chain is not in order\n"); + return FALSE; } } @@ -116,11 +115,9 @@ static CURLcode test_lib3102(const char *URL) struct curl_certinfo *cert_info = NULL; /* Get the certificate information */ result = curl_easy_getinfo(curl, CURLINFO_CERTINFO, &cert_info); - if(!result) { - /* Check to see if the certificate chain is ordered correctly */ - if(!is_chain_in_order(cert_info)) - result = TEST_ERR_FAILURE; - } + /* Check to see if the certificate chain is ordered correctly */ + if(!result && !is_chain_in_order(cert_info)) + result = TEST_ERR_FAILURE; } test_cleanup: diff --git a/tests/libtest/lib530.c b/tests/libtest/lib530.c index 94ecb7adf297..a5d953eeaaf4 100644 --- a/tests/libtest/lib530.c +++ b/tests/libtest/lib530.c @@ -361,12 +361,11 @@ static CURLcode testone(const char *URL, int timer_fail_at, int socket_fail_at) } if(timeout.tv_sec != (time_t)-1 && - t530_getMicroSecondTimeout(&timeout) == 0) { - /* curl's timer has elapsed. */ - if(socket_action(multi, CURL_SOCKET_TIMEOUT, 0, "timeout")) { - result = TEST_ERR_BAD_TIMEOUT; - goto test_cleanup; - } + t530_getMicroSecondTimeout(&timeout) == 0 && + /* curl's timer has elapsed. */ + socket_action(multi, CURL_SOCKET_TIMEOUT, 0, "timeout")) { + result = TEST_ERR_BAD_TIMEOUT; + goto test_cleanup; } abort_on_test_timeout(); diff --git a/tests/libtest/lib556.c b/tests/libtest/lib556.c index 1bc4638b9b29..cb8bbabfad36 100644 --- a/tests/libtest/lib556.c +++ b/tests/libtest/lib556.c @@ -76,15 +76,14 @@ static CURLcode test_lib556(const char *URL) /* busy-read like crazy */ result = curl_easy_recv(curl, buf, sizeof(buf), &nread); - if(nread) { - /* send received stuff to stdout */ - if((size_t)write(STDOUT_FILENO, buf, nread) != nread) { - char errbuf[STRERROR_LEN]; - curl_mfprintf(stderr, "write() failed: errno %d (%s)\n", - errno, curlx_strerror(errno, errbuf, sizeof(errbuf))); - result = TEST_ERR_FAILURE; - break; - } + if(nread && + /* send received stuff to stdout */ + (size_t)write(STDOUT_FILENO, buf, nread) != nread) { + char errbuf[STRERROR_LEN]; + curl_mfprintf(stderr, "write() failed: errno %d (%s)\n", + errno, curlx_strerror(errno, errbuf, sizeof(errbuf))); + result = TEST_ERR_FAILURE; + break; } } while((result == CURLE_OK && nread) || (result == CURLE_AGAIN)); diff --git a/tests/libtest/lib758.c b/tests/libtest/lib758.c index 794e4cce066d..379cc873faa3 100644 --- a/tests/libtest/lib758.c +++ b/tests/libtest/lib758.c @@ -455,12 +455,11 @@ static CURLcode t758_one(const char *URL, int timer_fail_at, } if(timeout.tv_sec != (time_t)-1 && - t758_getMicroSecondTimeout(&timeout) == 0) { - /* curl's timer has elapsed. */ - if(t758_saction(multi, CURL_SOCKET_TIMEOUT, 0, "timeout")) { - result = TEST_ERR_BAD_TIMEOUT; - goto test_cleanup; - } + t758_getMicroSecondTimeout(&timeout) == 0 && + /* curl's timer has elapsed. */ + t758_saction(multi, CURL_SOCKET_TIMEOUT, 0, "timeout")) { + result = TEST_ERR_BAD_TIMEOUT; + goto test_cleanup; } abort_on_test_timeout(); diff --git a/tests/server/socksd.c b/tests/server/socksd.c index 29fe56264e28..cd0bc2b9008c 100644 --- a/tests/server/socksd.c +++ b/tests/server/socksd.c @@ -703,15 +703,13 @@ static bool socksd_incoming(curl_socket_t listenfd) } for(i = 0; i < 2; i++) { struct perclient *cp = &c[i]; - if(cp->used) { - if(tunnel(cp, &fds_read)) { - logmsg("SOCKS transfer completed. Bytes: < %zu > %zu", - cp->fromremote, cp->fromclient); - sclose(cp->clientfd); - sclose(cp->remotefd); - cp->used = FALSE; - clients--; - } + if(cp->used && tunnel(cp, &fds_read)) { + logmsg("SOCKS transfer completed. Bytes: < %zu > %zu", + cp->fromremote, cp->fromclient); + sclose(cp->clientfd); + sclose(cp->remotefd); + cp->used = FALSE; + clients--; } } } while(clients); diff --git a/tests/unit/unit1627.c b/tests/unit/unit1627.c index cbdd3383086d..5150688df817 100644 --- a/tests/unit/unit1627.c +++ b/tests/unit/unit1627.c @@ -128,11 +128,9 @@ static CURLcode test_unit1627(const char *arg) for(i = 0; i < CURL_ARRAYSIZE(okay); i++) { char buffer[32]; const struct Curl_scheme *get = Curl_get_scheme(okay[i]); - if(get) { - /* verify that we got the correct scheme */ - if(!curl_strequal(get->name, okay[i])) - get = NULL; - } + /* verify that we got the correct scheme */ + if(get && !curl_strequal(get->name, okay[i])) + get = NULL; if(!get) { curl_mprintf("Input: %s, expected okay\n", okay[i]); break; diff --git a/tests/unit/unit1675.c b/tests/unit/unit1675.c index 1d7c44d46120..ca5b8e316b3c 100644 --- a/tests/unit/unit1675.c +++ b/tests/unit/unit1675.c @@ -227,14 +227,13 @@ static CURLcode test_unit1675(const char *arg) uc ? "error" : hostname); fails++; } - if(!uc && tests[i].out_zone) { - if(!u.zoneid || strcmp(u.zoneid, tests[i].out_zone)) { - curl_mfprintf(stderr, "ipv6_parse('%s') zone failed:" - " expected '%s', got '%s'\n", - tests[i].in, tests[i].out_zone, - u.zoneid ? u.zoneid : "(null)"); - fails++; - } + if(!uc && tests[i].out_zone && + (!u.zoneid || strcmp(u.zoneid, tests[i].out_zone))) { + curl_mfprintf(stderr, "ipv6_parse('%s') zone failed:" + " expected '%s', got '%s'\n", + tests[i].in, tests[i].out_zone, + u.zoneid ? u.zoneid : "(null)"); + fails++; } } else if(!uc) { diff --git a/tests/unit/unit2603.c b/tests/unit/unit2603.c index f2ec4f6b9036..0780567da1fc 100644 --- a/tests/unit/unit2603.c +++ b/tests/unit/unit2603.c @@ -80,12 +80,10 @@ static void parse_success(const struct tcase *t) fail("error consuming"); } in_consumed += nread; - if(nread != buflen) { - if(!p.done) { - curl_mfprintf(stderr, "only %zu/%zu consumed for: '%s'\n", - nread, buflen, buf); - fail("not all consumed"); - } + if(nread != buflen && !p.done) { + curl_mfprintf(stderr, "only %zu/%zu consumed for: '%s'\n", + nread, buflen, buf); + fail("not all consumed"); } } From 75e85f0e860d9a18ea895bdf10c744bb4d89c8f5 Mon Sep 17 00:00:00 2001 From: Ron Kuper Date: Mon, 24 Aug 2026 09:06:15 -0400 Subject: [PATCH 022/481] mprintf: stage output and emit it in runs alloc_addbyter() appended one byte at a time through curlx_dyn_addn(), paying its bounds checks, growth logic, memcpy and NUL-termination once per output byte, and formatf() had no way to hand an output function more than one byte at a time. Stage dynbuf output in a 512-byte buffer and append a chunk at a time, and add an optional block-output callback alongside the per-byte stream callback, so literal segments, %s bodies, digit runs and padding are emitted as runs. Each output function gets a memcpy-based variant: addrun(), alloc_addrun(), storerun() and fwrite_wrapper(). The dynbuf size limit is still reported at exactly the byte that exceeds it: alloc_addbyter() and alloc_addrun() check whether what they are about to stage can still fit and, when it cannot, flush and append that byte alone. A staged byte is not committed when it is accepted, so formatf() commits pending output before %n and performs it only if that succeeds. Without that, an allocation failure could leave %n executed where the byte-at-a-time path would have stopped short of it. stream_zrun(), which stops at a NUL, scans with a byte loop rather than memchr(), because the length it is given is the requested precision and is not clamped to the string length. curl_maprintf() of a request line: -73% on x86-64, -67% on a Cortex-A55, -68% on a 250MHz e300c3. Of one header line: -81%, -77%, -71%. Of a 300-byte all-%s body: -83%, -78%, -77%. Closes #22766 --- lib/mprintf.c | 359 +++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 294 insertions(+), 65 deletions(-) diff --git a/lib/mprintf.c b/lib/mprintf.c index d4725c905fe1..7abf487d6633 100644 --- a/lib/mprintf.c +++ b/lib/mprintf.c @@ -126,9 +126,17 @@ struct nsprintf { size_t max; }; +/* Output bytes are staged here and appended to the dynbuf in chunks: the + dynbuf append overhead (bounds checks, growth, memcpy, null termination) + once per byte dominates formatting. 512 bytes of stack holds a typical + request line or header in a single flush. */ +#define ASPRINTF_STAGE_SIZE 512 + struct asprintf { struct dynbuf *b; char merr; + size_t nstage; + unsigned char stage[ASPRINTF_STAGE_SIZE]; }; /* the provided input number is 1-based but this returns the number 0-based. @@ -643,8 +651,87 @@ struct mproperty { unsigned int flags; }; +/* block output callback: consume up to 'len' bytes, return the number of + bytes accepted. Accepting fewer than 'len' bytes aborts formatting, like + the byte callback returning failure. May be NULL, in which case output + falls back to the per-byte 'stream' callback. */ +typedef size_t (*mp_streamn)(const unsigned char *buf, size_t len, void *f); + +/* commit any output the sink is holding. Returns non-zero on failure. Sinks + whose accepted bytes are already committed do not need one. */ +typedef int (*mp_flush)(void *f); + +/* emit a run of bytes through the block callback when available, else byte + by byte. Returns TRUE to abort formatting (mirroring OUTCHAR). */ +static bool stream_run(void *userp, + int (*stream)(unsigned char, void *), + mp_streamn streamn, + const unsigned char *buf, size_t len, int *donep) +{ + if(!len) + return FALSE; + if(streamn) { + size_t n = streamn(buf, len, userp); + *donep += (int)n; + return n != len; + } + while(len--) { + if(stream(*buf++, userp)) + return TRUE; + (*donep)++; + } + return FALSE; +} + +/* Padding is always spaces or zeros, so it comes from a constant rather than a + buffer that has to be filled first. 16 keeps every width used in the tree to + a single run; wider padding loops. */ +static const char pad_spaces[] = " "; +static const char pad_zeros[] = "0000000000000000"; +#define PAD_RUN 16 /* strlen() of both of the above */ + +/* emit 'num' copies of the pad byte. Returns TRUE to abort formatting. */ +static bool stream_pad(void *userp, + int (*stream)(unsigned char, void *), + mp_streamn streamn, + unsigned char pad, int num, int *donep) +{ + const unsigned char *src; + if(num <= 0) + return FALSE; + if(pad == '0') + src = (const unsigned char *)pad_zeros; + else + src = (const unsigned char *)pad_spaces; + while(num > PAD_RUN) { + if(stream_run(userp, stream, streamn, src, PAD_RUN, donep)) + return TRUE; + num -= PAD_RUN; + } + return stream_run(userp, stream, streamn, src, (size_t)num, donep); +} + +/* emit the leading NUL-terminated part of a run of at most 'len' bytes. + Returns TRUE to abort formatting. + + A byte loop and not memchr(): 'len' is the requested precision, which + out_string() does not clamp to the string length, so "%.64s" on a 10-byte + string arrives with len == 64 and memchr() would read past the object. */ +static bool stream_zrun(void *userp, + int (*stream)(unsigned char, void *), + mp_streamn streamn, + const char *str, size_t len, int *donep) +{ + size_t n = 0; + while(n < len && str[n]) + n++; + return stream_run(userp, stream, streamn, + (const unsigned char *)str, n, donep); +} + static bool out_double(void *userp, int (*stream)(unsigned char, void *), + mp_streamn streamn, struct mproperty *p, double dnum, char *work, int *donep) @@ -727,16 +814,13 @@ static bool out_double(void *userp, #pragma GCC diagnostic pop #endif DEBUGASSERT(strlen(work) < BUFFSIZE); - while(*work) { - if(stream(*work++, userp)) - return TRUE; - (*donep)++; - } - return 0; + return stream_run(userp, stream, streamn, + (const unsigned char *)work, strlen(work), donep); } static bool out_number(void *userp, int (*stream)(unsigned char, void *), + mp_streamn streamn, struct mproperty *p, uint64_t num, int64_t nums, @@ -758,13 +842,16 @@ static bool out_number(void *userp, if(flags & FLAGS_CHAR) { /* Character. */ - if(!(flags & FLAGS_LEFT)) - while(--width > 0) - OUTCHAR(' '); + if(!(flags & FLAGS_LEFT)) { + if(stream_pad(userp, stream, streamn, ' ', width - 1, donep)) + return TRUE; + width = 1; + } OUTCHAR((char)num); - if(flags & FLAGS_LEFT) - while(--width > 0) - OUTCHAR(' '); + if(flags & FLAGS_LEFT) { + if(stream_pad(userp, stream, streamn, ' ', width - 1, donep)) + return TRUE; + } return FALSE; } if(flags & FLAGS_OCTAL) @@ -834,9 +921,11 @@ static bool out_number(void *userp, if(is_neg || (flags & FLAGS_SHOWSIGN) || (flags & FLAGS_SPACE)) --width; - if(!(flags & FLAGS_LEFT) && !(flags & FLAGS_PAD_NIL)) - while(width-- > 0) - OUTCHAR(' '); + if(!(flags & FLAGS_LEFT) && !(flags & FLAGS_PAD_NIL)) { + if(stream_pad(userp, stream, streamn, ' ', width, donep)) + return TRUE; + width = 0; + } if(is_neg) OUTCHAR('-'); @@ -853,18 +942,21 @@ static bool out_number(void *userp, OUTCHAR('x'); } - if(!(flags & FLAGS_LEFT) && (flags & FLAGS_PAD_NIL)) - while(width-- > 0) - OUTCHAR('0'); + if(!(flags & FLAGS_LEFT) && (flags & FLAGS_PAD_NIL)) { + if(stream_pad(userp, stream, streamn, '0', width, donep)) + return TRUE; + width = 0; + } /* Write the number. */ - while(++w <= workend) { - OUTCHAR(*w); - } + if(stream_run(userp, stream, streamn, (const unsigned char *)w + 1, + (size_t)(workend - w), donep)) + return TRUE; - if(flags & FLAGS_LEFT) - while(width-- > 0) - OUTCHAR(' '); + if(flags & FLAGS_LEFT) { + if(stream_pad(userp, stream, streamn, ' ', width, donep)) + return TRUE; + } return FALSE; } @@ -873,6 +965,7 @@ static const char nilstr[] = "(nil)"; static bool out_string(void *userp, int (*stream)(unsigned char, void *), + mp_streamn streamn, struct mproperty *p, const char *str, int *donep) @@ -907,15 +1000,18 @@ static bool out_string(void *userp, if(flags & FLAGS_ALT) OUTCHAR('"'); - if(!(flags & FLAGS_LEFT)) - while(width-- > 0) - OUTCHAR(' '); + if(!(flags & FLAGS_LEFT)) { + if(stream_pad(userp, stream, streamn, ' ', width, donep)) + return TRUE; + width = 0; + } - for(; len && *str; len--) - OUTCHAR(*str++); - if(flags & FLAGS_LEFT) - while(width-- > 0) - OUTCHAR(' '); + if(stream_zrun(userp, stream, streamn, str, len, donep)) + return TRUE; + if(flags & FLAGS_LEFT) { + if(stream_pad(userp, stream, streamn, ' ', width, donep)) + return TRUE; + } if(flags & FLAGS_ALT) OUTCHAR('"'); @@ -925,6 +1021,7 @@ static bool out_string(void *userp, static bool out_pointer(void *userp, int (*stream)(unsigned char, void *), + mp_streamn streamn, struct mproperty *p, const char *ptr, char *work, @@ -936,24 +1033,27 @@ static bool out_pointer(void *userp, /* If the pointer is not NULL, write it as a %#x spec. */ p->flags |= FLAGS_HEX | FLAGS_ALT; - if(out_number(userp, stream, p, num, 0, work, donep)) + if(out_number(userp, stream, streamn, p, num, 0, work, donep)) return TRUE; } else { /* Write "(nil)" for a nil pointer. */ - const char *point; int width = p->width; int flags = p->flags; width -= (int)CURL_CSTRLEN(nilstr); - if(flags & FLAGS_LEFT) - while(width-- > 0) - OUTCHAR(' '); - for(point = nilstr; *point; ++point) - OUTCHAR(*point); - if(!(flags & FLAGS_LEFT)) - while(width-- > 0) - OUTCHAR(' '); + if(flags & FLAGS_LEFT) { + if(stream_pad(userp, stream, streamn, ' ', width, donep)) + return TRUE; + width = 0; + } + if(stream_run(userp, stream, streamn, (const unsigned char *)nilstr, + sizeof(nilstr) - 1, donep)) + return TRUE; + if(!(flags & FLAGS_LEFT)) { + if(stream_pad(userp, stream, streamn, ' ', width, donep)) + return TRUE; + } } return FALSE; } @@ -977,6 +1077,10 @@ static int formatf(void *userp, /* untouched by format(), sent to the stream() function in the second argument */ /* function pointer called for each output character */ int (*stream)(unsigned char, void *), + /* optional block-output function pointer */ + mp_streamn streamn, + /* optional, commits output the sink is holding */ + mp_flush flush, const char *format, /* %-formatted string */ va_list ap_save) /* list of parameters */ { @@ -1000,12 +1104,8 @@ static int formatf(void *userp, /* untouched by format(), sent to the size_t outlen = optr->outlen; if(outlen) { - const char *str = optr->start; - for(; outlen && *str; outlen--) { - if(stream(*str++, userp)) - return done; - done++; - } + if(stream_zrun(userp, stream, streamn, optr->start, outlen, &done)) + return done; if(optr->flags & FLAGS_SUBSTR) /* this is a substring */ continue; @@ -1048,35 +1148,43 @@ static int formatf(void *userp, /* untouched by format(), sent to the case MTYPE_LONGU: case MTYPE_LONGLONGU: p.flags |= FLAGS_UNSIGNED; - if(out_number(userp, stream, &p, iptr->val.numu, 0, work, &done)) + if(out_number(userp, stream, streamn, &p, iptr->val.numu, 0, work, + &done)) return done; break; case MTYPE_INT: case MTYPE_LONG: case MTYPE_LONGLONG: - if(out_number(userp, stream, &p, iptr->val.numu, + if(out_number(userp, stream, streamn, &p, iptr->val.numu, iptr->val.nums, work, &done)) return done; break; case MTYPE_STRING: - if(out_string(userp, stream, &p, iptr->val.str, &done)) + if(out_string(userp, stream, streamn, &p, iptr->val.str, &done)) return done; break; case MTYPE_PTR: - if(out_pointer(userp, stream, &p, iptr->val.ptr, work, &done)) + if(out_pointer(userp, stream, streamn, &p, iptr->val.ptr, work, + &done)) return done; break; case MTYPE_DOUBLE: case MTYPE_LONGDOUBLE: - if(out_double(userp, stream, &p, iptr->val.dnum, work, &done)) + if(out_double(userp, stream, streamn, &p, iptr->val.dnum, work, + &done)) return done; break; case MTYPE_INTPTR: + /* %n is observable by the caller, so commit what is pending first and + perform it only if that succeeded, as a sink that never holds output + would. */ + if(flush && flush(userp)) + return done; /* Answer the count of characters written. */ if(p.flags & FLAGS_LONGLONG) *(int64_t *)iptr->val.ptr = (int64_t)done; @@ -1108,6 +1216,21 @@ static int addbyter(unsigned char outc, void *f) return 1; } +/* block variant of addbyter */ +static size_t addrun(const unsigned char *buf, size_t len, void *f) +{ + struct nsprintf *infop = f; + size_t fit = infop->max - infop->length; + if(len > fit) + len = fit; + if(len) { + memcpy(infop->buffer, buf, len); + infop->buffer += len; + infop->length += len; + } + return len; +} + int curl_mvsnprintf(char *buffer, size_t maxlength, const char *format, va_list args) { @@ -1118,7 +1241,7 @@ int curl_mvsnprintf(char *buffer, size_t maxlength, const char *format, info.length = 0; info.max = maxlength; - retcode = formatf(&info, addbyter, format, args); + retcode = formatf(&info, addbyter, addrun, NULL, format, args); if(info.max) { /* we terminate this with a zero byte */ if(info.max == info.length) { @@ -1143,11 +1266,27 @@ int curl_msnprintf(char *buffer, size_t maxlength, const char *format, ...) return retcode; } -/* fputc() look-alike */ -static int alloc_addbyter(unsigned char outc, void *f) +/* append the staged bytes to the dynbuf. Returns 1 on failure (and sets + merr), 0 on success. */ +static int alloc_flush(void *f) { struct asprintf *infop = f; - CURLcode result = curlx_dyn_addn(infop->b, &outc, 1); + if(infop->nstage) { + CURLcode result = curlx_dyn_addn(infop->b, infop->stage, infop->nstage); + infop->nstage = 0; + if(result) { + infop->merr = result == CURLE_TOO_LARGE ? MERR_TOO_LARGE : MERR_MEM; + return 1; /* fail */ + } + } + return 0; +} + +/* append directly, bypassing the stage. Returns 1 on failure. */ +static int alloc_addn(struct asprintf *infop, const unsigned char *buf, + size_t len) +{ + CURLcode result = curlx_dyn_addn(infop->b, buf, len); if(result) { infop->merr = result == CURLE_TOO_LARGE ? MERR_TOO_LARGE : MERR_MEM; return 1; /* fail */ @@ -1155,14 +1294,84 @@ static int alloc_addbyter(unsigned char outc, void *f) return 0; } +/* TRUE if 'more' additional bytes still fit the dynbuf size limit, counting + what is already staged. curlx_dyn_addn() rejects an append when + len + leng + 1 exceeds it. */ +static bool alloc_fits(struct asprintf *infop, size_t more) +{ + return (infop->b->leng + infop->nstage + more + 1) <= infop->b->toobig; +} + +/* fputc() look-alike */ +static int alloc_addbyter(unsigned char outc, void *f) +{ + struct asprintf *infop = f; + if(infop->nstage == sizeof(infop->stage) && alloc_flush(infop)) + return 1; /* fail */ + if(!alloc_fits(infop, 1)) { + /* Does not fit the dynbuf size limit. Flush and append this byte on + its own, so the limit is reported at exactly this byte and formatting + stops where it would without staging. %n depends on that. */ + if(alloc_flush(infop)) + return 1; + return alloc_addn(infop, &outc, 1); + } + infop->stage[infop->nstage++] = outc; + return 0; +} + +/* block variant of alloc_addbyter */ +static size_t alloc_addrun(const unsigned char *buf, size_t len, void *f) +{ + struct asprintf *infop = f; + size_t accepted = 0; + while(accepted < len) { + size_t room = sizeof(infop->stage) - infop->nstage; + size_t n; + if(!room) { + if(alloc_flush(infop)) + return accepted; /* accepted < len aborts formatting */ + room = sizeof(infop->stage); + } + n = len - accepted; + if(n > room) + n = room; + if(!alloc_fits(infop, n)) { + /* Run does not fit the dynbuf size limit. Stage what does, then + append the first byte that does not on its own, so the limit is + reported at exactly that byte. */ + size_t fits = infop->b->toobig - infop->b->leng - infop->nstage - 1; + if(fits) { + memcpy(&infop->stage[infop->nstage], buf + accepted, fits); + infop->nstage += fits; + accepted += fits; + } + if(alloc_flush(infop)) + return accepted; + if(alloc_addn(infop, buf + accepted, 1)) + return accepted; /* accepted < len aborts formatting */ + accepted++; + continue; + } + memcpy(&infop->stage[infop->nstage], buf + accepted, n); + infop->nstage += n; + accepted += n; + } + return accepted; +} + /* appends the formatted string, returns MERR error code */ int curlx_dyn_vprintf(struct dynbuf *dyn, const char *format, va_list args) { struct asprintf info; info.b = dyn; info.merr = MERR_OK; + info.nstage = 0; - (void)formatf(&info, alloc_addbyter, format, args); + (void)formatf(&info, alloc_addbyter, alloc_addrun, alloc_flush, + format, args); + if(!info.merr) + (void)alloc_flush(&info); if(info.merr) { curlx_dyn_free(info.b); return info.merr; @@ -1177,8 +1386,12 @@ char *curl_mvaprintf(const char *format, va_list args) info.b = &dyn; curlx_dyn_init(info.b, DYN_APRINTF); info.merr = MERR_OK; + info.nstage = 0; - (void)formatf(&info, alloc_addbyter, format, args); + (void)formatf(&info, alloc_addbyter, alloc_addrun, alloc_flush, + format, args); + if(!info.merr) + (void)alloc_flush(&info); if(info.merr) { curlx_dyn_free(info.b); return NULL; @@ -1206,12 +1419,21 @@ static int storebuffer(unsigned char outc, void *f) return 0; } +/* block variant of storebuffer */ +static size_t storerun(const unsigned char *buf, size_t len, void *f) +{ + char **buffer = f; + memcpy(*buffer, buf, len); + *buffer += len; + return len; +} + int curl_msprintf(char *buffer, const char *format, ...) { va_list args; /* argument pointer */ int retcode; va_start(args, format); - retcode = formatf(&buffer, storebuffer, format, args); + retcode = formatf(&buffer, storebuffer, storerun, NULL, format, args); va_end(args); *buffer = 0; /* we terminate this with a zero byte */ return retcode; @@ -1225,12 +1447,19 @@ static int fputc_wrapper(unsigned char outc, void *f) return rc == EOF; } +/* block variant of fputc_wrapper */ +static size_t fwrite_wrapper(const unsigned char *buf, size_t len, void *f) +{ + FILE *s = f; + return fwrite(buf, 1, len, s); +} + int curl_mprintf(const char *format, ...) { int retcode; va_list args; /* argument pointer */ va_start(args, format); - retcode = formatf(stdout, fputc_wrapper, format, args); + retcode = formatf(stdout, fputc_wrapper, fwrite_wrapper, NULL, format, args); va_end(args); return retcode; } @@ -1240,24 +1469,24 @@ int curl_mfprintf(FILE *fd, const char *format, ...) int retcode; va_list args; /* argument pointer */ va_start(args, format); - retcode = formatf(fd, fputc_wrapper, format, args); + retcode = formatf(fd, fputc_wrapper, fwrite_wrapper, NULL, format, args); va_end(args); return retcode; } int curl_mvsprintf(char *buffer, const char *format, va_list args) { - int retcode = formatf(&buffer, storebuffer, format, args); + int retcode = formatf(&buffer, storebuffer, storerun, NULL, format, args); *buffer = 0; /* we terminate this with a zero byte */ return retcode; } int curl_mvprintf(const char *format, va_list args) { - return formatf(stdout, fputc_wrapper, format, args); + return formatf(stdout, fputc_wrapper, fwrite_wrapper, NULL, format, args); } int curl_mvfprintf(FILE *fd, const char *format, va_list args) { - return formatf(fd, fputc_wrapper, format, args); + return formatf(fd, fputc_wrapper, fwrite_wrapper, NULL, format, args); } From 48e196426b148e6683914ce6a9206143175cb688 Mon Sep 17 00:00:00 2001 From: Daniel Stenberg Date: Tue, 1 Sep 2026 16:31:57 +0200 Subject: [PATCH 023/481] escape: polish encoding and decoding for speed Primarily thanks to a new loopup table and taking bigger chunks to work on instead of byte-by-byte, this PR does decoding twice as fast as before and it encodes five times faster (on my x86-64). Closes #22780 --- lib/escape.c | 143 +++++++++++++++++++++++++++++++--------- tests/data/test558 | 3 +- tests/libtest/lib1396.c | 4 ++ 3 files changed, 118 insertions(+), 32 deletions(-) diff --git a/lib/escape.c b/lib/escape.c index a1a714f8f592..0a1f0a953c42 100644 --- a/lib/escape.c +++ b/lib/escape.c @@ -44,13 +44,55 @@ char *curl_unescape(const char *string, int length) return curl_easy_unescape(NULL, string, length, NULL); } +#define NOPE 0xff +#define _OK_ 0x1f /* octet not needing encoding but not a hexadecimal + character */ + +static const unsigned char hextable[256] = { + NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, /* 00 */ + NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, /* 08 */ + NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, /* 10 */ + NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, /* 18 */ + NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, /* 20 */ + NOPE, NOPE, NOPE, NOPE, NOPE, _OK_, _OK_, NOPE, /* 28 */ + 0, 1, 2, 3, 4, 5, 6, 7, /* 30 */ + 8, 9, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, /* 38 */ + NOPE, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, _OK_, /* 40 */ + _OK_, _OK_, _OK_, _OK_, _OK_, _OK_, _OK_, _OK_, /* 48 */ + _OK_, _OK_, _OK_, _OK_, _OK_, _OK_, _OK_, _OK_, /* 50 */ + _OK_, _OK_, _OK_, NOPE, NOPE, NOPE, NOPE, _OK_, /* 58 */ + NOPE, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, _OK_, /* 60 */ + _OK_, _OK_, _OK_, _OK_, _OK_, _OK_, _OK_, _OK_, /* 68 */ + _OK_, _OK_, _OK_, _OK_, _OK_, _OK_, _OK_, _OK_, /* 70 */ + _OK_, _OK_, _OK_, NOPE, NOPE, NOPE, _OK_, NOPE, /* 78 */ + NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, /* 80 - not ASCII */ + NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, + NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, + NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, + NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, + NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, + NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, + NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, + NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, + NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, + NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, + NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, + NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, + NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, + NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, + NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE +}; + /* Escapes for URL the given unescaped string of given length. * 'data' is ignored since 7.82.0. */ char *curl_easy_escape(CURL *curl, const char *string, int length) { size_t len; - struct dynbuf d; + const char *f = string; + size_t nappend = 0; + char *target; + char *encoded; (void)curl; if(!string || (length < 0)) @@ -63,27 +105,37 @@ char *curl_easy_escape(CURL *curl, const char *string, int length) if(len > SIZE_MAX / 16) return NULL; - curlx_dyn_init(&d, (len * 3) + 1); + encoded = target = curlx_malloc((len * 3) + 1); + if(!target) + return NULL; while(len--) { - /* treat the characters unsigned */ - unsigned char in = (unsigned char)*string++; + uint8_t in = (uint8_t)*string++; - if(ISUNRESERVED(in)) { + if(!(hextable[in] & 0x80)) /* append this */ - if(curlx_dyn_addn(&d, &in, 1)) - return NULL; - } + nappend++; else { + if(nappend) { + memcpy(target, f, nappend); + target += nappend; + nappend = 0; + } + f = string; /* encode it */ - unsigned char out[3] = { '%' }; - Curl_hexbyte(&out[1], in); - if(curlx_dyn_addn(&d, out, 3)) - return NULL; + target[0] = '%'; + target[1] = Curl_udigits[in >> 4]; + target[2] = Curl_udigits[in & 0x0F]; + target += 3; } } + if(nappend) { + memcpy(target, f, nappend); + target += nappend; + } + *target = '\0'; /* null terminate */ - return curlx_dyn_ptr(&d); + return encoded; } /* @@ -108,6 +160,7 @@ CURLcode Curl_urldecode(const char *string, size_t length, { size_t alloc; char *ns; + uint8_t reject_limit; DEBUGASSERT(string); DEBUGASSERT(ctrl >= REJECT_NADA); /* crash on TRUE/FALSE */ @@ -121,36 +174,66 @@ CURLcode Curl_urldecode(const char *string, size_t length, /* store output string */ *ostring = ns; + reject_limit = (ctrl == REJECT_CTRL) ? 0x20 : + (ctrl == REJECT_ZERO) ? 1 : 0; + while(alloc) { - unsigned char in = (unsigned char)*string; - if(('%' == in) && (alloc > 2) && - ISXDIGIT(string[1]) && ISXDIGIT(string[2])) { - /* this is two hexadecimal digits following a '%' */ - in = (unsigned char)((curlx_hexval(string[1]) << 4) | - curlx_hexval(string[2])); - string += 3; - alloc -= 3; - } - else { + uint8_t in; + + if(*string == '%') { + if(alloc > 2) { + uint8_t h1 = hextable[(uint8_t)string[1]]; + uint8_t h2 = hextable[(uint8_t)string[2]]; + if(!((h1 | h2) & 0xf0)) { + in = (uint8_t)((h1 << 4) | h2); + string += 3; + alloc -= 3; + if(in < reject_limit) + goto error; + *ns++ = (char)in; + continue; + } + } + in = '%'; string++; alloc--; + *ns++ = (char)in; + continue; } + else { + const char *p = memchr(string + 1, '%', alloc - 1); + size_t n = p ? (size_t)(p - string) : alloc; - if(((ctrl == REJECT_CTRL) && (in < 0x20)) || - ((ctrl == REJECT_ZERO) && (in == 0))) { - curlx_safefree(*ostring); - return CURLE_URL_MALFORMAT; - } + if(reject_limit) { + if(reject_limit == 1) { + if(memchr(string, 0, n)) + goto error; + } + else { + size_t i; + for(i = 0; i < n; i++) { + if((uint8_t)string[i] < 0x20) + goto error; + } + } + } - *ns++ = (char)in; + memcpy(ns, string, n); + ns += n; + string += n; + alloc -= n; + } } *ns = 0; /* terminate it */ if(olen) /* store output size */ - *olen = ns - *ostring; + *olen = (size_t)(ns - *ostring); return CURLE_OK; +error: + curlx_safefree(*ostring); + return CURLE_URL_MALFORMAT; } /* diff --git a/tests/data/test558 b/tests/data/test558 index e6513ac5a30a..66e8769a019a 100644 --- a/tests/data/test558 +++ b/tests/data/test558 @@ -32,8 +32,7 @@ libtest memory tracking operational MEM easy.c: malloc() MEM lib%TESTNUMBER.c: malloc() MEM lib%TESTNUMBER.c: free() -MEM dynbuf.c: realloc() -MEM dynbuf.c: realloc() +MEM escape.c: malloc() MEM escape.c: free() MEM easy.c: free() diff --git a/tests/libtest/lib1396.c b/tests/libtest/lib1396.c index 60d139a8500f..902a859e27c1 100644 --- a/tests/libtest/lib1396.c +++ b/tests/libtest/lib1396.c @@ -69,6 +69,10 @@ static CURLcode test_lib1396(const char *arg) /* escape, this => that */ static const struct test list2[] = { { "a", 1, "a", 1 }, + { "-", 1, "-", 1 }, + { ".", 1, ".", 1 }, + { "_", 1, "_", 1 }, + { "~", 1, "~", 1 }, { "/", 1, "%2F", 3 }, { "a=b", 3, "a%3Db", 5 }, { "a=b", 0, "a%3Db", 5 }, From 2f897694d8526000bc0283b3660476d21171fa93 Mon Sep 17 00:00:00 2001 From: Max Dymond Date: Wed, 2 Sep 2026 17:38:55 +0100 Subject: [PATCH 024/481] multi: store timeout epoch as whole seconds The timeout splay tree stores its keys as offsets from a fixed epoch. Dropping the epoch's microseconds shifts every offset by the same constant, which cancels when calculating timeout deltas. Store only the epoch's seconds and reconstruct a zero-microsecond curltime for offset calculations. This reduces Curl_timeouts and Curl_multi by eight bytes on 64-bit builds without changing timeout ordering or durations. Closes #22806 --- lib/splay.c | 8 ++++++-- lib/splay.h | 2 +- tests/unit/unit1309.c | 45 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 52 insertions(+), 3 deletions(-) diff --git a/lib/splay.c b/lib/splay.c index 788da215ee54..e78317fbd368 100644 --- a/lib/splay.c +++ b/lib/splay.c @@ -30,8 +30,9 @@ void Curl_timeouts_init(struct Curl_timeouts *timeouts, const struct curltime *ptime_base) { + struct curltime time_base = ptime_base ? *ptime_base : curlx_now(); timeouts->tree = NULL; - timeouts->time_base = ptime_base ? *ptime_base : curlx_now(); + timeouts->time_base_sec = time_base.tv_sec; } bool Curl_timeouts_has(struct Curl_easy *data) @@ -43,7 +44,10 @@ bool Curl_timeouts_has(struct Curl_easy *data) timediff_t Curl_timeouts_offset_us(struct Curl_timeouts *timeouts, const struct curltime *pts) { - return curlx_ptimediff_us(pts, &timeouts->time_base); + struct curltime time_base; + time_base.tv_sec = timeouts->time_base_sec; + time_base.tv_usec = 0; + return curlx_ptimediff_us(pts, &time_base); } int Curl_timeouts_next_ms(struct Curl_timeouts *timeouts, diff --git a/lib/splay.h b/lib/splay.h index 3f9e66297cd2..f29cb0077b7a 100644 --- a/lib/splay.h +++ b/lib/splay.h @@ -41,7 +41,7 @@ struct Curl_tree { struct Curl_timeouts { struct Curl_tree *tree; - struct curltime time_base; + time_t time_base_sec; }; void Curl_timeouts_init(struct Curl_timeouts *timeouts, diff --git a/tests/unit/unit1309.c b/tests/unit/unit1309.c index 388c7d1415f3..1bb81359ac2e 100644 --- a/tests/unit/unit1309.c +++ b/tests/unit/unit1309.c @@ -54,6 +54,49 @@ static void splayprint(struct Curl_tree *t, int d, char output) splayprint(t->smaller, d + 1, output); } +static void test_timeouts_nonzero_usec_base(void) +{ + struct Curl_timeouts timeouts; + struct Curl_tree node; + struct curltime base; + struct curltime deadline; + struct curltime now; + timediff_t original_deadline_offset; + timediff_t original_now_offset; + timediff_t deadline_offset; + timediff_t now_offset; + timediff_t expire_offset; + uint32_t mid; + int timeout_ms; + + base.tv_sec = 10; + base.tv_usec = 750000; + deadline.tv_sec = 12; + deadline.tv_usec = 250000; + now.tv_sec = 11; + now.tv_usec = 100000; + + original_deadline_offset = curlx_ptimediff_us(&deadline, &base); + original_now_offset = curlx_ptimediff_us(&now, &base); + + Curl_timeouts_init(&timeouts, &base); + deadline_offset = Curl_timeouts_offset_us(&timeouts, &deadline); + now_offset = Curl_timeouts_offset_us(&timeouts, &now); + + fail_unless(deadline_offset - original_deadline_offset == base.tv_usec, + "deadline offset did not shift by the base microseconds"); + fail_unless(now_offset - original_now_offset == base.tv_usec, + "now offset did not shift by the base microseconds"); + + memset(&node, 0, sizeof(node)); + timeouts.tree = Curl_splayinsert(deadline_offset, timeouts.tree, &node, 42); + timeout_ms = Curl_timeouts_next_ms(&timeouts, &now, &expire_offset, &mid); + + fail_unless(timeout_ms == 1150, "timeout changed with whole-second base"); + fail_unless(expire_offset == deadline_offset, "wrong expiry offset"); + fail_unless(mid == 42, "wrong timeout id"); +} + static CURLcode test_unit1309(const char *arg) { UNITTEST_BEGIN_SIMPLE @@ -158,5 +201,7 @@ static CURLcode test_unit1309(const char *arg) fail_unless(!root, "tree not empty when it should be"); + test_timeouts_nonzero_usec_base(); + UNITTEST_END_SIMPLE } From 0f2e0af0da0dd31652cd73a7d5a52c0246416eec Mon Sep 17 00:00:00 2001 From: Viktor Szakats Date: Thu, 3 Sep 2026 11:00:08 +0200 Subject: [PATCH 025/481] GHA/non-native: re-enable FreeBSD riscv64 It seems to be working again, with no change on the curl end. Also: log the list of custom port sources. Refs: https://docs.freebsd.org/en/books/handbook/ports/ https://github.com/cross-platform-actions/freebsd-builder/commit/5d49278777bb3825aac3395d2dffc087f55ddf6d https://github.com/cross-platform-actions/action/issues/166 Follow-up to 73f18365ab8e47912afcd5ee9404c7493d9d0fb2 #22760 Closes #22814 --- .github/workflows/non-native.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/non-native.yml b/.github/workflows/non-native.yml index dfe0f16acbb0..b025aa9ea180 100644 --- a/.github/workflows/non-native.yml +++ b/.github/workflows/non-native.yml @@ -75,9 +75,9 @@ jobs: install: 'cmake-core ninja perl5 krb5-devel openldap26-client libidn2', options: '-DCURL_USE_GSSAPI=ON -DCMAKE_UNITY_BUILD=OFF' } - # { os: 'freebsd' , version: '15.1', build: 'cmake' , arch: 'riscv64', cc: 'clang', desc: 'openssl !examples', tflags: 'HTTP --min=0 --subset=0/10 -R --seed', - # install: 'cmake-core ninja perl5', - # options: '-D_CURL_PREFILL=ON -DBUILD_LIBCURL_DOCS=OFF -DBUILD_MISC_DOCS=OFF -DENABLE_CURL_MANUAL=OFF -DCURL_DISABLE_HTTPSIG=OFF -DCURL_DISABLE_TYPECHECK=ON' } + - { os: 'freebsd' , version: '15.1', build: 'cmake' , arch: 'riscv64', cc: 'clang', desc: 'openssl !examples', tflags: 'HTTP --min=0 --subset=0/10 -R --seed', + install: 'cmake-core ninja perl5', + options: '-D_CURL_PREFILL=ON -DBUILD_LIBCURL_DOCS=OFF -DBUILD_MISC_DOCS=OFF -DENABLE_CURL_MANUAL=OFF -DCURL_DISABLE_HTTPSIG=OFF -DCURL_DISABLE_TYPECHECK=ON' } - { os: 'freebsd' , version: '14.3', build: 'autotools', arch: 'arm64' , cc: 'clang', desc: 'openssl !examples', tflags: 'skipall', install: 'autoconf automake libtool krb5-devel openldap26-client libidn2 stunnel', @@ -136,6 +136,7 @@ jobs: if [ "${MATRIX_OS}" = 'dragonflybsd' ]; then sudo pkg install -y pkgconf brotli libnghttp2 ${MATRIX_INSTALL} elif [ "${MATRIX_OS}" = 'freebsd' ]; then + find /usr/local/etc/pkg/repos || true sudo pkg install -y pkgconf brotli libnghttp2 ${MATRIX_INSTALL} elif [ "${MATRIX_OS}" = 'midnightbsd' ]; then if [ "${MATRIX_BUILD}" = 'autotools' ]; then From 52ec4a95bfb88c5d3a96b2ddc60cecfd74049ff6 Mon Sep 17 00:00:00 2001 From: Stefan Eissing Date: Thu, 3 Sep 2026 11:15:36 +0200 Subject: [PATCH 026/481] connectdata: use less curltime Replace several curltime members with timediff_t in connectdata. Everyone needs a shrink! Closes #22815 --- lib/cfilters.c | 3 +-- lib/conncache.c | 34 ++++++++++++++++++---------------- lib/connect.c | 21 ++++++++++----------- lib/url.c | 20 ++++++++++++-------- lib/urldata.h | 9 +++++---- tests/unit/unit2606.c | 5 +++-- 6 files changed, 49 insertions(+), 43 deletions(-) diff --git a/lib/cfilters.c b/lib/cfilters.c index 6ffa262eb165..26152722ae18 100644 --- a/lib/cfilters.c +++ b/lib/cfilters.c @@ -179,8 +179,7 @@ void Curl_conn_cf_discard_chain(struct Curl_cfilter **pcf, void Curl_conn_cf_discard_all(struct Curl_easy *data, struct connectdata *conn, int8_t sockindex) { - struct curltime *pt = &conn->shutdown.start[sockindex]; - memset(pt, 0, sizeof(*pt)); + conn->shutdown.start_ms[sockindex] = 0; Curl_conn_cf_discard_chain(&conn->cfilter[sockindex], data); } diff --git a/lib/conncache.c b/lib/conncache.c index 01681d4ebbc9..84a335ae9b92 100644 --- a/lib/conncache.c +++ b/lib/conncache.c @@ -315,9 +315,9 @@ static struct connectdata *cpool_bundle_get_oldest_idle( const struct curltime *pnow) { struct Curl_llist_node *curr; - timediff_t highscore = -1; - timediff_t score; struct connectdata *oldest_idle = NULL; + timediff_t unused_ms; + timediff_t oldest_ms = -1; struct connectdata *conn; curr = Curl_llist_head(&bundle->conns); @@ -326,10 +326,9 @@ static struct connectdata *cpool_bundle_get_oldest_idle( if(!CONN_INUSE(conn)) { /* Set higher score for the age passed since the connection was used */ - score = curlx_ptimediff_ms(pnow, &conn->lastused); - - if(score > highscore) { - highscore = score; + unused_ms = curlx_ptimediff_ms(pnow, &conn->created) - conn->lastused_ms; + if(unused_ms > oldest_ms) { + oldest_ms = unused_ms; oldest_idle = conn; } } @@ -362,7 +361,7 @@ static struct connectdata *cpool_get_oldest_idle(struct cpool *cpool, conn = Curl_node_elem(curr); if(CONN_INUSE(conn) || conn->bits.close || conn->bits.connect_only) continue; - idle_ms = curlx_ptimediff_ms(pnow, &conn->lastused); + idle_ms = curlx_ptimediff_ms(pnow, &conn->created) - conn->lastused_ms; if((idle_ms >= min_age_ms) && (idle_ms > oldest_idle_ms)) { oldest_idle_ms = idle_ms; oldest_idle = conn; @@ -641,10 +640,11 @@ static bool cpool_foreach(struct Curl_easy *data, bool Curl_cpool_conn_now_idle(struct Curl_easy *data, struct connectdata *conn) { - unsigned int maxconnects; - struct connectdata *oldest_idle = NULL; struct cpool *cpool = cpool_get_instance(data); + struct connectdata *oldest_idle = NULL; + const struct curltime *pnow = NULL; struct Curl_easy *admin; + unsigned int maxconnects; bool kept = TRUE; timediff_t min_age_ms = 0; @@ -664,7 +664,9 @@ bool Curl_cpool_conn_now_idle(struct Curl_easy *data, } /* remember times, connection had been used just before */ - conn->lastchecked = conn->lastupkeep = conn->lastused = *Curl_pgrs_now(data); + pnow = Curl_pgrs_now(data); + conn->lastchecked_ms = conn->lastupkeep_ms = conn->lastused_ms = + curlx_ptimediff_ms(pnow, &conn->created); if(cpool && maxconnects) { /* may be called form a callback already under lock */ bool do_lock = !CPOOL_IS_LOCKED(cpool); @@ -676,7 +678,7 @@ bool Curl_cpool_conn_now_idle(struct Curl_easy *data, infof(data, "Connection pool is full, closing the oldest of %zu/%u", cpool->num_conn, maxconnects); - oldest_idle = cpool_get_oldest_idle(cpool, &conn->lastused, min_age_ms); + oldest_idle = cpool_get_oldest_idle(cpool, pnow, min_age_ms); kept = (oldest_idle != conn); if(oldest_idle) { cpool_evict_conn(cpool, admin, oldest_idle); @@ -792,11 +794,11 @@ static int conn_upkeep(struct cpool *cpool, const struct curltime *pnow = Curl_pgrs_now(admin); (void)param; - if(curlx_ptimediff_ms(pnow, &conn->lastupkeep) >= + if((curlx_ptimediff_ms(pnow, &conn->created) - conn->lastupkeep_ms) >= admin->set.upkeep_interval_ms) { CURLcode result; - conn->lastupkeep = *pnow; + conn->lastupkeep_ms = curlx_ptimediff_ms(pnow, &conn->created); /* briefly attach for action */ Curl_attach_connection(admin, conn, FALSE); result = Curl_conn_keep_alive(admin, conn); @@ -918,7 +920,7 @@ static bool cpool_conn_maxage(struct Curl_easy *data, timediff_t age_ms; if(data->set.conn_max_idle_ms) { - age_ms = curlx_ptimediff_ms(pnow, &conn->lastused); + age_ms = curlx_ptimediff_ms(pnow, &conn->created) - conn->lastused_ms; if(age_ms > data->set.conn_max_idle_ms) { infof(data, "Too old connection (%" FMT_TIMEDIFF_T " ms idle, max idle is %" FMT_TIMEDIFF_T " ms), disconnect it", @@ -951,7 +953,7 @@ bool Curl_cpool_conn_seems_healthy(struct connectdata *conn, DEBUGASSERT(!data->conn); if(!CONN_INUSE(conn) && cpool_conn_maxage(data, conn, pnow)) /* too old? */ return FALSE; - if(curlx_ptimediff_ms(pnow, &conn->lastchecked) < 1000) + if((curlx_ptimediff_ms(pnow, &conn->created) - conn->lastchecked_ms) < 1000) return TRUE; admin = Curl_get_admin(data); @@ -977,7 +979,7 @@ bool Curl_cpool_conn_seems_healthy(struct connectdata *conn, } if(healthy) - conn->lastchecked = *pnow; + conn->lastchecked_ms = curlx_ptimediff_ms(pnow, &conn->created); return healthy; } diff --git a/lib/connect.c b/lib/connect.c index e91c2167f982..e312a809f53e 100644 --- a/lib/connect.c +++ b/lib/connect.c @@ -119,7 +119,8 @@ void Curl_shutdown_start(struct Curl_easy *data, int8_t sockindex, const struct curltime *pnow = Curl_pgrs_now(data); DEBUGASSERT(conn); - conn->shutdown.start[sockindex] = *pnow; + conn->shutdown.start_ms[sockindex] = + curlx_ptimediff_ms(pnow, &conn->created); conn->shutdown.timeout_ms = (timeout_ms > 0) ? (timediff_t)timeout_ms : ((data->set.shutdowntimeout > 0) ? @@ -137,13 +138,12 @@ timediff_t Curl_shutdown_timeleft(struct Curl_easy *data, { timediff_t left_ms; - if(!conn->shutdown.start[sockindex].tv_sec || - (conn->shutdown.timeout_ms <= 0)) + if(!conn->shutdown.start_ms[sockindex]) return 0; /* not started or no limits */ left_ms = conn->shutdown.timeout_ms - - curlx_ptimediff_ms(Curl_pgrs_now(data), - &conn->shutdown.start[sockindex]); + (curlx_ptimediff_ms(Curl_pgrs_now(data), &conn->created) - + conn->shutdown.start_ms[sockindex]); return left_ms ? left_ms : -1; } @@ -154,7 +154,7 @@ timediff_t Curl_conn_shutdown_timeleft(struct Curl_easy *data, int8_t i; for(i = 0; conn->shutdown.timeout_ms && (i < 2); ++i) { - if(!conn->shutdown.start[i].tv_sec) + if(!conn->shutdown.start_ms[i]) continue; ms = Curl_shutdown_timeleft(data, conn, i); if(ms && (!left_ms || ms < left_ms)) @@ -165,14 +165,12 @@ timediff_t Curl_conn_shutdown_timeleft(struct Curl_easy *data, void Curl_shutdown_clear(struct Curl_easy *data, int8_t sockindex) { - struct curltime *pt = &data->conn->shutdown.start[sockindex]; - memset(pt, 0, sizeof(*pt)); + data->conn->shutdown.start_ms[sockindex] = 0; } bool Curl_shutdown_started(struct connectdata *conn, int8_t sockindex) { - const struct curltime *pt = &conn->shutdown.start[sockindex]; - return (pt->tv_sec > 0) || (pt->tv_usec > 0); + return conn->shutdown.start_ms[sockindex] > 0; } /* @@ -379,7 +377,8 @@ CURLcode Curl_conn_connect(struct Curl_easy *data, * socket and ip related information. */ Curl_conn_cntrl_update_info(data, data->conn); conn_report_stats(data, sockindex); - data->conn->lastupkeep = *Curl_pgrs_now(data); + data->conn->lastupkeep_ms = + curlx_ptimediff_ms(Curl_pgrs_now(data), &data->conn->created); VERBOSE(result = conn_connect_trace(data, cf)); VERBOSE(Curl_conn_trc_filters(data, sockindex, "connected")); Curl_conn_remove_setup_filters(data, sockindex); diff --git a/lib/url.c b/lib/url.c index 00b77be1649f..a4bca235c9f1 100644 --- a/lib/url.c +++ b/lib/url.c @@ -1176,7 +1176,8 @@ static bool url_attach_existing(struct Curl_easy *data, /* * Allocate and initialize a new connectdata object. */ -static struct connectdata *allocate_conn(struct Curl_easy *data) +static struct connectdata *allocate_conn(struct Curl_easy *data, + const struct curltime *pnow) { struct connectdata *conn = curlx_calloc(1, sizeof(struct connectdata)); if(!conn) @@ -1192,7 +1193,7 @@ static struct connectdata *allocate_conn(struct Curl_easy *data) conn->attached_xfers = 0; /* Remember time this connection started */ - conn->lastused = conn->lastupkeep = conn->created = *Curl_pgrs_now(data); + conn->created = *pnow; #ifndef CURL_DISABLE_FTP conn->bits.ftp_use_epsv = data->set.ftp_use_epsv; @@ -1999,6 +2000,7 @@ static void conn_meta_freeentry(void *p) } static CURLcode url_create_needle(struct Curl_easy *data, + const struct curltime *pnow, struct connectdata **pneedle) { struct connectdata *needle = NULL; @@ -2007,7 +2009,7 @@ static CURLcode url_create_needle(struct Curl_easy *data, /* Allocate a temporary connection data struct (needle) and fill in for comparison purposes. */ - needle = allocate_conn(data); + needle = allocate_conn(data, pnow); if(!needle) { result = CURLE_OUT_OF_MEMORY; goto out; @@ -2237,7 +2239,8 @@ static CURLcode url_set_data_origin_and_creds(struct Curl_easy *data) * a suitable connection has not determined its multiplex capability. * - a fatal error */ -static CURLcode url_find_or_create_conn(struct Curl_easy *data) +static CURLcode url_find_or_create_conn(struct Curl_easy *data, + const struct curltime *pnow) { struct connectdata *needle = NULL; bool waitpipe = FALSE; @@ -2246,7 +2249,7 @@ static CURLcode url_find_or_create_conn(struct Curl_easy *data) /* create the template connection for transfer data. Use this needle to * find an existing connection or, if none exists, convert needle * to a full connection and attach it to data. */ - result = url_create_needle(data, &needle); + result = url_create_needle(data, pnow, &needle); if(result) goto out; DEBUGASSERT(needle); @@ -2437,7 +2440,7 @@ CURLcode Curl_connect(struct Curl_easy *data, bool *pconnected) { CURLcode result; struct connectdata *conn = NULL; - + const struct curltime *pnow = NULL; *pconnected = FALSE; /* Set the request to virgin state based on transfer settings */ @@ -2453,7 +2456,9 @@ CURLcode Curl_connect(struct Curl_easy *data, bool *pconnected) } /* Get or create a connection for the transfer. */ - result = url_find_or_create_conn(data); + pnow = Curl_pgrs_now(data); + Curl_pgrsTimeWas(data, TIMER_POSTQUEUE, *pnow); + result = url_find_or_create_conn(data, pnow); conn = data->conn; if(result) goto out; @@ -2463,7 +2468,6 @@ CURLcode Curl_connect(struct Curl_easy *data, bool *pconnected) goto out; } - Curl_pgrsTime(data, TIMER_POSTQUEUE); if(conn->bits.reuse) { if(conn->attached_xfers > 1) /* multiplexed */ diff --git a/lib/urldata.h b/lib/urldata.h index 1c0101847b94..06407857fd52 100644 --- a/lib/urldata.h +++ b/lib/urldata.h @@ -292,9 +292,10 @@ struct connectdata { char *destination; /* hostname+port, used in conncache */ struct curltime created; /* creation time */ - struct curltime lastused; /* when returned to the connection pool as idle */ - struct curltime lastchecked; /* when last checked alive status */ - struct curltime lastupkeep; /* when last done conn_upkeep */ + /* timediff_t are deltas from `created`*/ + timediff_t lastused_ms; /* when returned to the connection pool as idle */ + timediff_t lastchecked_ms; /* when last checked alive status */ + timediff_t lastupkeep_ms; /* when last done conn_upkeep */ #ifndef CURL_DISABLE_PROXY struct proxy_info socks_proxy; @@ -311,7 +312,7 @@ struct connectdata { #define CONN_SOCK_IDX_VALID(i) (((i) >= 0) && ((i) < 2)) struct { - struct curltime start[2]; /* when filter shutdown started */ + timediff_t start_ms[2]; /* when filter shutdown started */ timediff_t timeout_ms; /* 0 means no timeout */ } shutdown; diff --git a/tests/unit/unit2606.c b/tests/unit/unit2606.c index ebe8b275fd89..a4db5def4b68 100644 --- a/tests/unit/unit2606.c +++ b/tests/unit/unit2606.c @@ -130,6 +130,7 @@ static void t2606_run(struct Curl_easy *data, const struct t2606_case *tc) conn = curlx_calloc(1, sizeof(*conn)); abort_if(!conn, "could not allocate a connection"); + conn->created = curlx_now(); memset(&ctx0, 0, sizeof(ctx0)); memset(&ctx1, 0, sizeof(ctx1)); ctx0.sock = 42; @@ -142,7 +143,7 @@ static void t2606_run(struct Curl_easy *data, const struct t2606_case *tc) Curl_conn_cf_add(data, conn, FIRSTSOCKET, cf0); cf0->connected = tc->c0_connected; if(tc->c0_shutdown) - conn->shutdown.start[FIRSTSOCKET] = curlx_now(); + conn->shutdown.start_ms[FIRSTSOCKET] = 100; } if(tc->has_c1) { @@ -152,7 +153,7 @@ static void t2606_run(struct Curl_easy *data, const struct t2606_case *tc) Curl_conn_cf_add(data, conn, SECONDARYSOCKET, cf1); cf1->connected = tc->c1_connected; if(tc->c1_shutdown) - conn->shutdown.start[SECONDARYSOCKET] = curlx_now(); + conn->shutdown.start_ms[SECONDARYSOCKET] = 100; } } From 38703fe2610bf6e79390bfc93a8558ffc0e2b02c Mon Sep 17 00:00:00 2001 From: Stefan Eissing Date: Wed, 2 Sep 2026 14:22:17 +0200 Subject: [PATCH 027/481] multihandle: some easy shrinks - new `struct Curl_fixed_buf` keeping data and length in one alloc for upload/download/socket buffers held in the multi - max_host_connections changed to uint32_t - max_total_connections changed to uint32_t - maxconnects changed to uint32_t - last_timeout_ms changed to timediff_t - now_access_count removed, no one used it Closes #22801 --- lib/multi.c | 81 ++++++++++++++++++++++++------------------- lib/multihandle.h | 47 +++++++++++-------------- lib/multiif.h | 2 +- lib/url.c | 2 +- lib/vquic/cf-ngtcp2.c | 6 ++-- 5 files changed, 72 insertions(+), 66 deletions(-) diff --git a/lib/multi.c b/lib/multi.c index dc0743456f52..7d6243b3e4c1 100644 --- a/lib/multi.c +++ b/lib/multi.c @@ -3345,6 +3345,7 @@ CURLMcode curl_multi_setopt(CURLM *m, CURLMoption option, ...) struct Curl_multi *multi = m; va_list param; unsigned long uarg; + size_t szarg; va_start(param, option); @@ -3372,16 +3373,20 @@ CURLMcode curl_multi_setopt(CURLM *m, CURLMoption option, ...) break; case CURLMOPT_MAXCONNECTS: uarg = va_arg(param, unsigned long); - if(uarg <= UINT_MAX) - multi->maxconnects = (unsigned int)uarg; + if(uarg <= UINT32_MAX) + multi->maxconnects = (uint32_t)uarg; break; case CURLMOPT_MAX_HOST_CONNECTIONS: - if(!curlx_sltouz(va_arg(param, long), &multi->max_host_connections)) + if(!curlx_sltouz(va_arg(param, long), &szarg)) mresult = CURLM_BAD_FUNCTION_ARGUMENT; + multi->max_host_connections = (szarg < UINT32_MAX) ? + (uint32_t)szarg : UINT32_MAX; break; case CURLMOPT_MAX_TOTAL_CONNECTIONS: - if(!curlx_sltouz(va_arg(param, long), &multi->max_total_connections)) + if(!curlx_sltouz(va_arg(param, long), &szarg)) mresult = CURLM_BAD_FUNCTION_ARGUMENT; + multi->max_total_connections = (szarg < UINT32_MAX) ? + (uint32_t)szarg : UINT32_MAX; break; /* options formerly used for pipelining */ case CURLMOPT_MAX_PIPELINE_LENGTH: @@ -3395,10 +3400,12 @@ CURLMcode curl_multi_setopt(CURLM *m, CURLMoption option, ...) case CURLMOPT_PIPELINING_SERVER_BL: break; case CURLMOPT_MAX_CONCURRENT_STREAMS: { - long streams = va_arg(param, long); - if((streams < 1) || (streams > INT_MAX)) - streams = 100; - multi->max_concurrent_streams = (unsigned int)streams; + if(!curlx_sltouz(va_arg(param, long), &szarg) || + !szarg || (szarg > (size_t)INT_MAX)) /* preserve previous cutoff */ + multi->max_concurrent_streams = 100; + else + multi->max_concurrent_streams = (szarg < UINT32_MAX) ? + (uint32_t)szarg : UINT32_MAX; break; } case CURLMOPT_NETWORK_CHANGED: { @@ -3906,7 +3913,7 @@ static void multi_schedule_pending(struct Curl_multi *multi) } } -unsigned int Curl_multi_max_concurrent_streams(struct Curl_multi *multi) +uint32_t Curl_multi_max_concurrent_streams(struct Curl_multi *multi) { DEBUGASSERT(multi); return multi->max_concurrent_streams; @@ -3991,6 +3998,17 @@ CURLMcode curl_multi_get_offt(CURLM *m, return mresult; } +static struct Curl_fixed_buf *fixed_buf_create(size_t len) +{ + struct Curl_fixed_buf *fbuf; + if((SIZE_MAX - sizeof(*fbuf)) < len) + return NULL; /* too large */ + fbuf = curlx_malloc(len + sizeof(*fbuf)); + if(fbuf) + fbuf->len = len; + return fbuf; +} + CURLcode Curl_multi_xfer_buf_borrow(struct Curl_easy *data, char **pbuf, size_t *pbuflen) { @@ -4012,25 +4030,24 @@ CURLcode Curl_multi_xfer_buf_borrow(struct Curl_easy *data, } if(data->multi->xfer_buf && - data->set.buffer_size > data->multi->xfer_buf_len) { + data->set.buffer_size > data->multi->xfer_buf->len) { /* not large enough, get a new one */ curlx_safefree(data->multi->xfer_buf); - data->multi->xfer_buf_len = 0; } if(!data->multi->xfer_buf) { - data->multi->xfer_buf = curlx_malloc(curlx_uitouz(data->set.buffer_size)); + data->multi->xfer_buf = + fixed_buf_create(curlx_uitouz(data->set.buffer_size)); if(!data->multi->xfer_buf) { failf(data, "could not allocate xfer_buf of %u bytes", data->set.buffer_size); return CURLE_OUT_OF_MEMORY; } - data->multi->xfer_buf_len = data->set.buffer_size; } data->multi->xfer_buf_borrowed = TRUE; - *pbuf = data->multi->xfer_buf; - *pbuflen = data->multi->xfer_buf_len; + *pbuf = data->multi->xfer_buf->data; + *pbuflen = data->multi->xfer_buf->len; return CURLE_OK; } @@ -4039,7 +4056,8 @@ void Curl_multi_xfer_buf_release(struct Curl_easy *data, char *buf) (void)buf; DEBUGASSERT(data); DEBUGASSERT(data->multi); - DEBUGASSERT(!buf || data->multi->xfer_buf == buf); + DEBUGASSERT(!buf || (data->multi->xfer_buf && + data->multi->xfer_buf->data == buf)); data->multi->xfer_buf_borrowed = FALSE; } @@ -4054,36 +4072,30 @@ CURLcode Curl_multi_xfer_ulbuf_borrow(struct Curl_easy *data, failf(data, "transfer has no multi handle"); return CURLE_FAILED_INIT; } - if(!data->set.upload_buffer_size) { - failf(data, "transfer upload buffer size is 0"); - return CURLE_FAILED_INIT; - } if(data->multi->xfer_ulbuf_borrowed) { failf(data, "attempt to borrow xfer_ulbuf when already borrowed"); return CURLE_AGAIN; } if(data->multi->xfer_ulbuf && - data->set.upload_buffer_size > data->multi->xfer_ulbuf_len) { + data->set.upload_buffer_size > data->multi->xfer_ulbuf->len) { /* not large enough, get a new one */ curlx_safefree(data->multi->xfer_ulbuf); - data->multi->xfer_ulbuf_len = 0; } if(!data->multi->xfer_ulbuf) { data->multi->xfer_ulbuf = - curlx_malloc(curlx_uitouz(data->set.upload_buffer_size)); + fixed_buf_create(curlx_uitouz(data->set.upload_buffer_size)); if(!data->multi->xfer_ulbuf) { failf(data, "could not allocate xfer_ulbuf of %u bytes", data->set.upload_buffer_size); return CURLE_OUT_OF_MEMORY; } - data->multi->xfer_ulbuf_len = data->set.upload_buffer_size; } data->multi->xfer_ulbuf_borrowed = TRUE; - *pbuf = data->multi->xfer_ulbuf; - *pbuflen = data->multi->xfer_ulbuf_len; + *pbuf = data->multi->xfer_ulbuf->data; + *pbuflen = data->multi->xfer_ulbuf->len; return CURLE_OK; } @@ -4092,7 +4104,8 @@ void Curl_multi_xfer_ulbuf_release(struct Curl_easy *data, char *buf) (void)buf; DEBUGASSERT(data); DEBUGASSERT(data->multi); - DEBUGASSERT(!buf || data->multi->xfer_ulbuf == buf); + DEBUGASSERT(!buf || (data->multi->xfer_ulbuf && + data->multi->xfer_ulbuf->data == buf)); data->multi->xfer_ulbuf_borrowed = FALSE; } @@ -4112,23 +4125,21 @@ CURLcode Curl_multi_xfer_sockbuf_borrow(struct Curl_easy *data, return CURLE_AGAIN; } - if(data->multi->xfer_sockbuf && blen > data->multi->xfer_sockbuf_len) { + if(data->multi->xfer_sockbuf && blen > data->multi->xfer_sockbuf->len) { /* not large enough, get a new one */ curlx_safefree(data->multi->xfer_sockbuf); - data->multi->xfer_sockbuf_len = 0; } if(!data->multi->xfer_sockbuf) { - data->multi->xfer_sockbuf = curlx_malloc(blen); + data->multi->xfer_sockbuf = fixed_buf_create(blen); if(!data->multi->xfer_sockbuf) { failf(data, "could not allocate xfer_sockbuf of %zu bytes", blen); return CURLE_OUT_OF_MEMORY; } - data->multi->xfer_sockbuf_len = blen; } data->multi->xfer_sockbuf_borrowed = TRUE; - *pbuf = data->multi->xfer_sockbuf; + *pbuf = data->multi->xfer_sockbuf->data; return CURLE_OK; } @@ -4141,7 +4152,8 @@ void Curl_multi_xfer_sockbuf_release(struct Curl_easy *data, char *buf) curlx_free(buf); } else { - DEBUGASSERT(!buf || data->multi->xfer_sockbuf == buf); + DEBUGASSERT(!buf || (data->multi->xfer_sockbuf && + data->multi->xfer_sockbuf->data == buf)); data->multi->xfer_sockbuf_borrowed = FALSE; } } @@ -4150,13 +4162,10 @@ static void multi_xfer_bufs_free(struct Curl_multi *multi) { DEBUGASSERT(multi); curlx_safefree(multi->xfer_buf); - multi->xfer_buf_len = 0; multi->xfer_buf_borrowed = FALSE; curlx_safefree(multi->xfer_ulbuf); - multi->xfer_ulbuf_len = 0; multi->xfer_ulbuf_borrowed = FALSE; curlx_safefree(multi->xfer_sockbuf); - multi->xfer_sockbuf_len = 0; multi->xfer_sockbuf_borrowed = FALSE; } diff --git a/lib/multihandle.h b/lib/multihandle.h index 2a8fba158300..daa2b4b44402 100644 --- a/lib/multihandle.h +++ b/lib/multihandle.h @@ -84,6 +84,11 @@ typedef enum { /* value for MAXIMUM CONCURRENT STREAMS upper limit */ #define INITIAL_MAX_CONCURRENT_STREAMS ((1U << 31) - 1) +struct Curl_fixed_buf { + size_t len; /* the allocated data length */ + char data[1]; /* the actual buffer */ +}; + /* This is the struct known as CURLM on the outside */ struct Curl_multi { /* First a simple identifier to more easily detect if a user mixes up @@ -95,6 +100,7 @@ struct Curl_multi { uint32_t xfers_really_alive; /* amount of added transfers that have passed INIT state but are not COMPLETE yet */ uint32_t max_concurrent_streams; + curl_off_t xfers_total_ever; /* total of added transfers, ever. */ struct uint32_tbl xfers; /* transfers added to this multi */ /* Each transfer's mid may be present in at most one of these */ @@ -105,9 +111,12 @@ struct Curl_multi { struct Curl_mapi_stack callstack; /* multi api calls ongoing */ - struct Curl_llist msglist; /* a list of messages from completed transfers */ + /* current time for transfers running in this multi handle */ + struct curltime now; + /* expiration times for all attached easy handles */ + struct Curl_timeouts timeouts; - curl_off_t xfers_total_ever; /* total of added transfers, ever. */ + struct Curl_llist msglist; /* a list of messages from completed transfers */ struct Curl_easy *admin; /* internal easy handle for admin operations. gets assigned `mid` 0 on multi init */ @@ -131,20 +140,9 @@ struct Curl_multi { struct PslCache psl; #endif - /* current time for transfers running in this multi handle */ - struct curltime now; - /* expiration times for all attached easy handles */ - struct Curl_timeouts timeouts; - - /* buffer used for transfer data, lazy initialized */ - char *xfer_buf; /* the actual buffer */ - size_t xfer_buf_len; /* the allocated length */ - /* buffer used for upload data, lazy initialized */ - char *xfer_ulbuf; /* the actual buffer */ - size_t xfer_ulbuf_len; /* the allocated length */ - /* buffer used for socket I/O operations, lazy initialized */ - char *xfer_sockbuf; /* the actual buffer */ - size_t xfer_sockbuf_len; /* the allocated length */ + struct Curl_fixed_buf *xfer_buf; /* transfer downloads, lazy */ + struct Curl_fixed_buf *xfer_ulbuf; /* transfer uploads, lazy */ + struct Curl_fixed_buf *xfer_sockbuf; /* transfer socket I/O, lazy */ /* multi event related things */ struct curl_multi_ev ev; @@ -161,17 +159,13 @@ struct Curl_multi { struct cshutdn cshutdn; /* connection shutdown handling */ struct cpool cpool; /* connection pool (bundles) */ - timediff_t last_expire_offset_us; /* times offset of last expiry */ - size_t max_host_connections; /* if >0, a fixed limit of the maximum number - of connections per host */ - size_t max_total_connections; /* if >0, a fixed limit of the maximum number - of connections in total */ + timediff_t last_expire_offset_us; /* times offset of last expiry */ /* timer callback and user data pointer for the *socket() API */ curl_multi_timer_callback timer_cb; void *timer_userp; - int last_timeout_ms; /* the last timeout value set via timer_cb */ + timediff_t last_timeout_ms; /* the last timeout value set via timer_cb */ #ifdef USE_WINSOCK WSAEVENT wsa_event; /* Winsock event used for waits */ @@ -187,12 +181,13 @@ struct Curl_multi { for write. Used for internal wakeups, e.g. threaded resolver. */ #endif - unsigned int maxconnects; /* if >0, a fixed limit of the maximum number of + uint32_t max_host_connections; /* if >0, a fixed limit of the maximum number + of connections per host */ + uint32_t max_total_connections; /* if >0, a fixed limit of the maximum number + of connections in total */ + uint32_t maxconnects; /* if >0, a fixed limit of the maximum number of entries we are allowed to grow the connection cache to */ -#ifdef DEBUGBUILD - unsigned int now_access_count; -#endif uint32_t last_pending_mid; /* mid of last pending transfer rescheduled */ uint32_t last_resolv_id; /* id of the last DNS resolve operation */ BIT(ipv6_works); diff --git a/lib/multiif.h b/lib/multiif.h index d3ad8fffb334..538933f1c415 100644 --- a/lib/multiif.h +++ b/lib/multiif.h @@ -73,7 +73,7 @@ CURLMcode Curl_multi_add_perform(struct Curl_multi *multi, struct connectdata *conn); /* Return the value of the CURLMOPT_MAX_CONCURRENT_STREAMS option */ -unsigned int Curl_multi_max_concurrent_streams(struct Curl_multi *multi); +uint32_t Curl_multi_max_concurrent_streams(struct Curl_multi *multi); CURLMcode Curl_multi_pollset(struct Curl_easy *data, struct easy_pollset *ps); diff --git a/lib/url.c b/lib/url.c index a4bca235c9f1..45c03da9da10 100644 --- a/lib/url.c +++ b/lib/url.c @@ -2357,7 +2357,7 @@ static CURLcode url_find_or_create_conn(struct Curl_easy *data, CURL_TRC_M(data, "Allowing sub-requests (like DoH) to override " "max connection limit"); else { - infof(data, "No connections available, total of %zu reached.", + infof(data, "No connections available, total of %u reached.", data->multi->max_total_connections); result = CURLE_NO_CONNECTION_AVAILABLE; goto out; diff --git a/lib/vquic/cf-ngtcp2.c b/lib/vquic/cf-ngtcp2.c index e3bca8774e40..0ebf849789b2 100644 --- a/lib/vquic/cf-ngtcp2.c +++ b/lib/vquic/cf-ngtcp2.c @@ -1053,8 +1053,10 @@ static CURLcode cf_ngtcp2_query(struct Curl_cfilter *cf, max_streams += avail_bidi_streams; *pres1 = (max_streams > INT_MAX) ? INT_MAX : (int)max_streams; } - else /* transport params not arrived yet? take our default. */ - *pres1 = (int)Curl_multi_max_concurrent_streams(data->multi); + else { /* transport params not arrived yet? take our default. */ + uint32_t n = Curl_multi_max_concurrent_streams(data->multi); + *pres1 = (n < INT_MAX) ? (int)n : INT_MAX; + } CURL_TRC_CF(data, cf, "query conn[%" FMT_OFF_T "]: " "MAX_CONCURRENT -> %d (%u in use)", cf->conn->connection_id, *pres1, cf->conn->attached_xfers); From 73c0180cb6823e01518a33e74ec0a6ce7f80d4d4 Mon Sep 17 00:00:00 2001 From: Max Dymond Date: Wed, 2 Sep 2026 17:16:59 +0100 Subject: [PATCH 028/481] multi: remove redundant completion message list Use the msgsent bitset to track unread application messages and scan it from curl_multi_info_read(). Remove entries when read or when their easy handle is removed. This removes the linked-list node from each easy handle. Completion order is not API-defined. Add regression coverage for CURLMINFO_XFERS_DONE. Closes #22805 --- docs/internals/MID.md | 11 ++-- lib/multi.c | 78 +++++++++--------------- lib/multihandle.h | 13 +--- lib/urldata.h | 2 +- tests/data/Makefile.am | 1 + tests/data/test2453 | 52 ++++++++++++++++ tests/libtest/Makefile.inc | 1 + tests/libtest/lib2453.c | 118 +++++++++++++++++++++++++++++++++++++ 8 files changed, 210 insertions(+), 66 deletions(-) create mode 100644 tests/data/test2453 create mode 100644 tests/libtest/lib2453.c diff --git a/docs/internals/MID.md b/docs/internals/MID.md index 5f1105008dbf..fddc3d3a2fab 100644 --- a/docs/internals/MID.md +++ b/docs/internals/MID.md @@ -52,8 +52,9 @@ The table is implemented in `uint-table.[ch]`. More details in [`UINT_SETS`](UIN There are several places where transfers need to be tracked: -* the multi tracks `process`, `pending` and `msgsent` transfers. A transfer - is in at most one of these at a time. +* the multi tracks `process`, `pending` and `msgsent` transfers. A transfer is + in at most one of these at a time. A `dirty` set, which may overlap with + `process`, tracks transfers that need to run immediately. * connections track the transfers that are *attached* to them. * multi event handling tracks transfers interested in a specific socket. * DoH handles track the handle they perform lookups for (and vice versa). @@ -63,9 +64,9 @@ The first is a bitset optimal for storing a large number of unsigned int values. The second one is a "sparse" variant good for storing a small set of numbers. More details about these in [`UINT_SETS`](UINT_SETS.md). -A multi uses `uint_bset`s for `process`, `pending` and `msgsent`. Connections -and sockets use the sparse variant as both often track only a single transfer -and at most 100 on an HTTP/2 or HTTP/3 connection/socket. +A multi uses `uint_bset`s for `process`, `dirty`, `pending` and `msgsent`. +Connections and sockets use the sparse variant as both often track only a +single transfer and at most 100 on an HTTP/2 or HTTP/3 connection/socket. These sets allow safe iteration while being modified. This allows a multi to iterate over its "process" set while existing transfers are removed diff --git a/lib/multi.c b/lib/multi.c index 7d6243b3e4c1..ae7a37509ff8 100644 --- a/lib/multi.c +++ b/lib/multi.c @@ -210,14 +210,13 @@ static void ph_freeentry(void *p) /* * multi_addmsg() * - * Called when a transfer is completed. Adds the given msg pointer to - * the list kept in the multi handle. + * Called when a transfer is completed. Marks its message as unread. */ -static void multi_addmsg(struct Curl_multi *multi, struct Curl_message *msg) +static void multi_addmsg(struct Curl_multi *multi, struct Curl_easy *data) { - if(!Curl_llist_count(&multi->msglist)) + if(Curl_uint32_bset_empty(&multi->msgsent)) CURLM_NTFY(multi->admin, CURLMNOTIFY_INFO_READ); - Curl_llist_append(&multi->msglist, msg, &msg->list); + Curl_uint32_bset_add(&multi->msgsent, data->mid); } static void multi_timeouts_init(struct Curl_easy *data); @@ -262,7 +261,6 @@ struct Curl_multi *Curl_multi_handle(uint32_t xfer_table_size, Curl_uint32_bset_init(&multi->msgsent); Curl_hash_init(&multi->proto_hash, 23, Curl_hash_str, curlx_str_key_compare, ph_freeentry); - Curl_llist_init(&multi->msglist, NULL); multi->multiplexing = TRUE; multi->max_concurrent_streams = 100; @@ -765,7 +763,6 @@ CURLMcode Curl_multi_remove_handle(struct Curl_multi *multi, { CURLMcode mresult; bool premature; - struct Curl_llist_node *e; uint32_t mid; /* Prevent users from trying to remove same easy handle more than once */ @@ -805,8 +802,8 @@ CURLMcode Curl_multi_remove_handle(struct Curl_multi *multi, called. Do it after multi_done() in case that sets another time! */ Curl_expire_clear_all(data); - /* If in `msgsent`, it was deducted from `multi->xfers_alive` already. */ - if(!Curl_uint32_bset_contains(&multi->msgsent, data->mid)) + /* In MSGSENT, it was deducted from `multi->xfers_alive` already. */ + if(data->mstate != MSTATE_MSGSENT) --multi->xfers_alive; if(data->state.really_alive) { @@ -857,18 +854,6 @@ CURLMcode Curl_multi_remove_handle(struct Curl_multi *multi, data->psl = NULL; #endif - /* make sure there is no pending message in the queue sent from this easy - handle */ - for(e = Curl_llist_head(&multi->msglist); e; e = Curl_node_next(e)) { - struct Curl_message *msg = Curl_node_elem(e); - - if(msg->extmsg.easy_handle == data) { - Curl_node_remove(e); - /* there can only be one from this specific handle */ - break; - } - } - /* clear the association to this multi handle */ mid = data->mid; DEBUGASSERT(Curl_uint32_tbl_contains(&multi->xfers, mid)); @@ -2432,8 +2417,10 @@ static void handle_completed(struct Curl_multi *multi, struct Curl_easy *data, CURLcode result) { - if(data->master_mid != UINT32_MAX) { - /* A sub transfer, not for msgsent to application. Is anyone still + bool msg_to_app = data->master_mid == UINT32_MAX; + + if(!msg_to_app) { + /* A sub transfer, not reported to the application. Is anyone still * interested in processing its results? */ if(data->sub_xfer_done) { struct Curl_easy *master = Curl_multi_get_easy(multi, data->master_mid); @@ -2446,23 +2433,23 @@ static void handle_completed(struct Curl_multi *multi, } } else { - /* now fill in the Curl_message with this info */ - struct Curl_message *msg = &data->msg; + /* now fill in the CURLMsg with this info */ + struct CURLMsg *msg = &data->msg; - msg->extmsg.msg = CURLMSG_DONE; - msg->extmsg.easy_handle = data; - msg->extmsg.data.result = result; + msg->msg = CURLMSG_DONE; + msg->easy_handle = data; + msg->data.result = result; - multi_addmsg(multi, msg); DEBUGASSERT(!data->conn); } multistate(data, MSTATE_MSGSENT); - /* remove from the other sets, add to msgsent */ + /* remove from the other sets */ Curl_uint32_bset_remove(&multi->process, data->mid); Curl_uint32_bset_remove(&multi->dirty, data->mid); Curl_uint32_bset_remove(&multi->pending, data->mid); - Curl_uint32_bset_add(&multi->msgsent, data->mid); + if(msg_to_app) + multi_addmsg(multi, data); if(data->state.really_alive) { data->state.really_alive = FALSE; --multi->xfers_really_alive; @@ -3097,10 +3084,7 @@ CURLMcode curl_multi_cleanup(CURLM *m) * curl_multi_info_read() * * This function is the primary way for a multi/multi_socket application to - * figure out if a transfer has ended. We MUST make this function as fast as - * possible as it will be polled frequently and we MUST NOT scan any lists in - * here to figure out things. We must scale fine to thousands of handles and - * beyond. The current design is fully O(1). + * figure out if a transfer has ended. */ CURLMsg *curl_multi_info_read(CURLM *m, int *msgs_in_queue) @@ -3111,22 +3095,16 @@ CURLMsg *curl_multi_info_read(CURLM *m, int *msgs_in_queue) *msgs_in_queue = 0; /* default to none */ if(CURL_MAPI_ENTER(&guard, m, multi_info_read, NULL)) { struct Curl_multi *multi = m; - if(Curl_llist_count(&multi->msglist)) { - /* there is one or more messages in the list */ - struct Curl_llist_node *e; - struct Curl_message *msg; - - /* extract the head of the list to return */ - e = Curl_llist_head(&multi->msglist); - - msg = Curl_node_elem(e); - - /* remove the extracted entry */ - Curl_node_remove(e); - - *msgs_in_queue = curlx_uztosi(Curl_llist_count(&multi->msglist)); + uint32_t mid; + if(Curl_uint32_bset_first(&multi->msgsent, &mid)) { + struct Curl_easy *data = Curl_multi_get_easy(multi, mid); - msg_result = &msg->extmsg; + DEBUGASSERT(data); + Curl_uint32_bset_remove(&multi->msgsent, mid); + *msgs_in_queue = + curlx_uztosi(Curl_uint32_bset_count(&multi->msgsent)); + if(data) + msg_result = &data->msg; } } CURL_MAPI_LEAVE(&guard); diff --git a/lib/multihandle.h b/lib/multihandle.h index daa2b4b44402..7f56ff2bf0d6 100644 --- a/lib/multihandle.h +++ b/lib/multihandle.h @@ -41,12 +41,6 @@ struct connectdata; struct Curl_easy; -struct Curl_message { - struct Curl_llist_node list; - /* the 'CURLMsg' is the part that is visible to the external user */ - struct CURLMsg extmsg; -}; - /* NOTE: if you add a state here, add the name to the statenames[] array * in curl_trc.c as well! */ @@ -103,11 +97,12 @@ struct Curl_multi { curl_off_t xfers_total_ever; /* total of added transfers, ever. */ struct uint32_tbl xfers; /* transfers added to this multi */ - /* Each transfer's mid may be present in at most one of these */ + /* A transfer's mid may be present in at most one of + * process/pending/msgsent. The dirty set may overlap with process. */ struct uint32_bset process; /* transfer being processed */ struct uint32_bset dirty; /* transfer to be run NOW, e.g. ASAP. */ struct uint32_bset pending; /* transfers in waiting (conn limit etc.) */ - struct uint32_bset msgsent; /* transfers done with message for application */ + struct uint32_bset msgsent; /* transfers with unread application messages */ struct Curl_mapi_stack callstack; /* multi api calls ongoing */ @@ -116,8 +111,6 @@ struct Curl_multi { /* expiration times for all attached easy handles */ struct Curl_timeouts timeouts; - struct Curl_llist msglist; /* a list of messages from completed transfers */ - struct Curl_easy *admin; /* internal easy handle for admin operations. gets assigned `mid` 0 on multi init */ diff --git a/lib/urldata.h b/lib/urldata.h index 06407857fd52..cd50c5135272 100644 --- a/lib/urldata.h +++ b/lib/urldata.h @@ -1179,7 +1179,7 @@ struct Curl_easy { struct Curl_multi *multi_easy; /* if non-NULL, points to the multi handle struct to which this "belongs" when used by the easy interface */ - struct Curl_message msg; /* A single posted message. */ + struct CURLMsg msg; /* A single posted message. */ /* once an easy handle is tied to a connection pool a non-negative number to distinguish this transfer from other using the same pool. For easier diff --git a/tests/data/Makefile.am b/tests/data/Makefile.am index 3b32fce7bb30..9372af5ee555 100644 --- a/tests/data/Makefile.am +++ b/tests/data/Makefile.am @@ -1960,6 +1960,7 @@ TESTCASES = \ test2412 \ test2413 \ test2414 \ + test2453 \ \ test2500 \ test2501 \ diff --git a/tests/data/test2453 b/tests/data/test2453 new file mode 100644 index 000000000000..8456b9e95c9a --- /dev/null +++ b/tests/data/test2453 @@ -0,0 +1,52 @@ + + + + +HTTP +HTTP GET +CURLMINFO_XFERS_DONE +curl_multi_info_read + + + +# Server-side + + +HTTP/1.1 200 OK +Date: Tue, 09 Nov 2010 14:49:00 GMT +Server: test-server/fake +Content-Length: 0 +Connection: close + + + + +# Client-side + + +http + + +http + + +CURLMINFO_XFERS_DONE tracks unread completion messages + + +lib%TESTNUMBER + + +http://%HOSTIP:%HTTPPORT/%TESTNUMBER + + + +# Verify data after the test has been "shot" + + +GET /%TESTNUMBER HTTP/1.1 +Host: %HOSTIP:%HTTPPORT +Accept: */* + + + + diff --git a/tests/libtest/Makefile.inc b/tests/libtest/Makefile.inc index 26c72e5672ec..cb0e7ea9b04e 100644 --- a/tests/libtest/Makefile.inc +++ b/tests/libtest/Makefile.inc @@ -306,6 +306,7 @@ TESTS_C = \ lib2405.c \ lib2412.c \ lib2414.c \ + lib2453.c \ lib2502.c \ lib2504.c \ lib2505.c \ diff --git a/tests/libtest/lib2453.c b/tests/libtest/lib2453.c new file mode 100644 index 000000000000..ace5f034629e --- /dev/null +++ b/tests/libtest/lib2453.c @@ -0,0 +1,118 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ +#include "first.h" + +static CURLcode expect_xfers_done(CURLM *multi, curl_off_t expected) +{ + curl_off_t value = -1; + CURLMcode mresult = curl_multi_get_offt(multi, CURLMINFO_XFERS_DONE, + &value); + + if(mresult != CURLM_OK) { + curl_mfprintf(stderr, "curl_multi_get_offt() failed with code %d\n", + (int)mresult); + return TEST_ERR_MULTI; + } + if(value != expected) { + curl_mfprintf(stderr, + "Expected %" CURL_FORMAT_CURL_OFF_T + " unread completion messages, got %" + CURL_FORMAT_CURL_OFF_T "\n", + expected, value); + return TEST_ERR_FAILURE; + } + return CURLE_OK; +} + +static CURLcode test_lib2453(const char *URL) +{ + CURL *curl = NULL; + CURLM *multi = NULL; + CURLcode result = CURLE_OK; + CURLMsg *msg; + int msgs_left; + int still_running; + + start_test_timing(); + global_init(CURL_GLOBAL_ALL); + multi_init(multi); + easy_init(curl); + + result = expect_xfers_done(multi, 0); + if(result) + goto test_cleanup; + + easy_setopt(curl, CURLOPT_URL, URL); + multi_add_handle(multi, curl); + + do { + CURLMcode mresult; + int numfds; + + multi_perform(multi, &still_running); + if(!still_running) + break; + + mresult = curl_multi_poll(multi, NULL, 0, TEST_HANG_TIMEOUT, &numfds); + if(mresult != CURLM_OK) { + curl_mfprintf(stderr, "curl_multi_poll() failed with code %d\n", + (int)mresult); + result = TEST_ERR_MULTI; + goto test_cleanup; + } + abort_on_test_timeout(); + } while(still_running); + + result = expect_xfers_done(multi, 1); + if(result) + goto test_cleanup; + + msg = curl_multi_info_read(multi, &msgs_left); + if(!msg || msg->msg != CURLMSG_DONE || msg->easy_handle != curl || + msg->data.result != CURLE_OK || msgs_left) { + curl_mfprintf(stderr, "Unexpected completion message\n"); + result = TEST_ERR_FAILURE; + goto test_cleanup; + } + + result = expect_xfers_done(multi, 0); + if(result) + goto test_cleanup; + + multi_remove_handle(multi, curl); + multi_perform(multi, &still_running); + if(still_running) { + curl_mfprintf(stderr, "Completed transfer still counted as running\n"); + result = TEST_ERR_FAILURE; + } + +test_cleanup: + if(multi && curl) + curl_multi_remove_handle(multi, curl); + curl_easy_cleanup(curl); + curl_multi_cleanup(multi); + curl_global_cleanup(); + + return result; +} From e26f6e408a80dc0adcb704b0e2c408e240f482cc Mon Sep 17 00:00:00 2001 From: Max Dymond Date: Thu, 3 Sep 2026 11:39:47 +0100 Subject: [PATCH 029/481] uint-bset: skip unused slots when counting Start counting at first_slot_used to avoid scanning the known-empty prefix of the bitset. --- lib/uint-bset.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/uint-bset.c b/lib/uint-bset.c index 55aedb234b05..82b84e3b5af8 100644 --- a/lib/uint-bset.c +++ b/lib/uint-bset.c @@ -80,7 +80,7 @@ uint32_t Curl_uint32_bset_count(struct uint32_bset *bset) { uint32_t i; uint32_t n = 0; - for(i = 0; i < bset->nslots; ++i) { + for(i = bset->first_slot_used; i < bset->nslots; ++i) { if(bset->slots[i]) n += CURL_POPCOUNT64(bset->slots[i]); } From 2956af6c7451029fb12c12f8166dff211f930d52 Mon Sep 17 00:00:00 2001 From: Stefan Eissing Date: Thu, 3 Sep 2026 17:54:45 +0200 Subject: [PATCH 030/481] HTTP-CONNECT: do not react to 401 responses When a proxy answers with a 401 on a CONNECT, ignore Authentication headers and fail the connect. Add test2173 to verify. Closes #22817 --- lib/cf-h1-proxy.c | 6 ++-- lib/http_proxy.c | 5 +--- tests/data/Makefile.am | 2 ++ tests/data/test2173 | 68 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 73 insertions(+), 8 deletions(-) create mode 100644 tests/data/test2173 diff --git a/lib/cf-h1-proxy.c b/lib/cf-h1-proxy.c index 49b157bb3ad8..71472cb3af4d 100644 --- a/lib/cf-h1-proxy.c +++ b/lib/cf-h1-proxy.c @@ -307,8 +307,7 @@ static CURLcode on_resp_header_udp(struct Curl_cfilter *cf, CURLcode result = CURLE_OK; struct SingleRequest *k = &data->req; - if((checkprefix("WWW-Authenticate:", header) && (401 == k->httpcode)) || - (checkprefix("Proxy-authenticate:", header) && (407 == k->httpcode))) { + if(checkprefix("Proxy-authenticate:", header) && (407 == k->httpcode)) { bool proxy = (k->httpcode == 407); char *auth = Curl_copy_header_value(header); @@ -394,8 +393,7 @@ static CURLcode on_resp_header(struct Curl_cfilter *cf, struct SingleRequest *k = &data->req; (void)cf; - if((checkprefix("WWW-Authenticate:", header) && (401 == k->httpcode)) || - (checkprefix("Proxy-authenticate:", header) && (407 == k->httpcode))) { + if(checkprefix("Proxy-authenticate:", header) && (407 == k->httpcode)) { bool proxy = (k->httpcode == 407); char *auth = Curl_copy_header_value(header); diff --git a/lib/http_proxy.c b/lib/http_proxy.c index 7774589ecd64..f620acb0cdb5 100644 --- a/lib/http_proxy.c +++ b/lib/http_proxy.c @@ -489,10 +489,7 @@ CURLcode Curl_http_proxy_inspect_tunnel_response( infof(data, " %s: %s", entry->name, entry->value); } - if(resp->status == 401) { - auth_reply = Curl_dynhds_cget(&resp->headers, "WWW-Authenticate"); - } - else if(resp->status == 407) { + if(resp->status == 407) { auth_reply = Curl_dynhds_cget(&resp->headers, "Proxy-Authenticate"); } diff --git a/tests/data/Makefile.am b/tests/data/Makefile.am index 9372af5ee555..add0dccd775d 100644 --- a/tests/data/Makefile.am +++ b/tests/data/Makefile.am @@ -1918,6 +1918,8 @@ TESTCASES = \ test2118 \ test2119 \ \ + test2173 \ + \ test2200 \ test2201 \ test2202 \ diff --git a/tests/data/test2173 b/tests/data/test2173 new file mode 100644 index 000000000000..c264c9387ac8 --- /dev/null +++ b/tests/data/test2173 @@ -0,0 +1,68 @@ + + + + +HTTP +HTTP GET +HTTP CONNECT +HTTP Basic auth +proxytunnel + + +# Server-side + + +HTTP/1.1 200 OK +Date: Tue, 09 Nov 2010 14:49:00 GMT +Server: test-server/fake swsclose +Content-Type: text/html +Funny-head: yesyes +Content-Length: 9 + +contents + + +HTTP/1.1 401 Tricking You +WWW-Authenticate: Basic + + + +HTTP/1.1 401 Tricking You +WWW-Authenticate: Basic + + + + +# Client-side + + +http +http-proxy + + +HTTP over proxy-tunnel with site wrong 401 response + + +http://test.%TESTNUMBER:%HTTPPORT/we/want/that/page/%TESTNUMBER -p -x %HOSTIP:%PROXYPORT --user 'iam:my:;self' --anyauth + + +proxy + + + +# Verify data after the test has been "shot" + + +CONNECT test.%TESTNUMBER:%HTTPPORT HTTP/1.1 +Host: test.%TESTNUMBER:%HTTPPORT +User-Agent: curl/%VERSION +Proxy-Connection: Keep-Alive + + + + + +7 + + + From 970f47328b4ad993331e49c5e29f91d65ac364b2 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 4 Sep 2026 03:01:32 +0000 Subject: [PATCH 031/481] GHA: update dependency google/boringssl to v0.20260903.0 Closes #22820 --- .github/workflows/http3-linux.yml | 2 +- .github/workflows/linux.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/http3-linux.yml b/.github/workflows/http3-linux.yml index ca2bad2ea053..59077b9ae96f 100644 --- a/.github/workflows/http3-linux.yml +++ b/.github/workflows/http3-linux.yml @@ -41,7 +41,7 @@ env: # renovate: datasource=github-tags depName=awslabs/aws-lc versioning=semver registryUrl=https://github.com AWSLC_VERSION: 5.5.0 # renovate: datasource=github-tags depName=google/boringssl versioning=semver registryUrl=https://github.com - BORINGSSL_VERSION: 0.20260730.0 + BORINGSSL_VERSION: 0.20260903.0 # renovate: datasource=github-tags depName=gnutls/nettle versioning=semver registryUrl=https://github.com NETTLE_VERSION: 3.10.2 # renovate: datasource=github-tags depName=gnutls/gnutls versioning=semver extractVersion=^nettle_?(?.+)_release_.+$ registryUrl=https://github.com diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index a2d610219f56..d854858af5dc 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -40,7 +40,7 @@ env: # renovate: datasource=github-tags depName=awslabs/aws-lc versioning=semver registryUrl=https://github.com AWSLC_VERSION: 5.5.0 # renovate: datasource=github-tags depName=google/boringssl versioning=semver registryUrl=https://github.com - BORINGSSL_VERSION: 0.20260730.0 + BORINGSSL_VERSION: 0.20260903.0 # renovate: datasource=github-releases depName=pizlonator/fil-c versioning=semver-coerced registryUrl=https://github.com FIL_C_VERSION: 0.684 # renovate: datasource=github-tags depName=libressl/portable versioning=semver registryUrl=https://github.com From 9a012386b818da9530953da1967e6d01cc39e479 Mon Sep 17 00:00:00 2001 From: Stefan Eissing Date: Fri, 4 Sep 2026 09:07:37 +0200 Subject: [PATCH 032/481] connect: fix for first ms connection shutdown When a connection needed to start shutdown in the first millisecond since its creation, the changed `start_ms` indicator did not change. Initialize `start_ms` with -1 so that 0ms are treated as a shutdown start. Closes #22821 --- lib/cfilters.c | 2 +- lib/connect.c | 8 ++++---- lib/url.c | 6 +++--- tests/unit/unit2606.c | 3 +++ 4 files changed, 11 insertions(+), 8 deletions(-) diff --git a/lib/cfilters.c b/lib/cfilters.c index 26152722ae18..ab96268a9e08 100644 --- a/lib/cfilters.c +++ b/lib/cfilters.c @@ -179,7 +179,7 @@ void Curl_conn_cf_discard_chain(struct Curl_cfilter **pcf, void Curl_conn_cf_discard_all(struct Curl_easy *data, struct connectdata *conn, int8_t sockindex) { - conn->shutdown.start_ms[sockindex] = 0; + conn->shutdown.start_ms[sockindex] = -1; Curl_conn_cf_discard_chain(&conn->cfilter[sockindex], data); } diff --git a/lib/connect.c b/lib/connect.c index e312a809f53e..56bf46ae4691 100644 --- a/lib/connect.c +++ b/lib/connect.c @@ -138,7 +138,7 @@ timediff_t Curl_shutdown_timeleft(struct Curl_easy *data, { timediff_t left_ms; - if(!conn->shutdown.start_ms[sockindex]) + if(conn->shutdown.start_ms[sockindex] < 0) return 0; /* not started or no limits */ left_ms = conn->shutdown.timeout_ms - @@ -154,7 +154,7 @@ timediff_t Curl_conn_shutdown_timeleft(struct Curl_easy *data, int8_t i; for(i = 0; conn->shutdown.timeout_ms && (i < 2); ++i) { - if(!conn->shutdown.start_ms[i]) + if(conn->shutdown.start_ms[i] < 0) continue; ms = Curl_shutdown_timeleft(data, conn, i); if(ms && (!left_ms || ms < left_ms)) @@ -165,12 +165,12 @@ timediff_t Curl_conn_shutdown_timeleft(struct Curl_easy *data, void Curl_shutdown_clear(struct Curl_easy *data, int8_t sockindex) { - data->conn->shutdown.start_ms[sockindex] = 0; + data->conn->shutdown.start_ms[sockindex] = -1; } bool Curl_shutdown_started(struct connectdata *conn, int8_t sockindex) { - return conn->shutdown.start_ms[sockindex] > 0; + return conn->shutdown.start_ms[sockindex] >= 0; } /* diff --git a/lib/url.c b/lib/url.c index 45c03da9da10..14557a46fe2d 100644 --- a/lib/url.c +++ b/lib/url.c @@ -1185,15 +1185,15 @@ static struct connectdata *allocate_conn(struct Curl_easy *data, /* and we setup a few fields in case we end up actually using this struct */ + conn->created = *pnow; conn->sock[FIRSTSOCKET] = CURL_SOCKET_BAD; /* no file descriptor */ conn->sock[SECONDARYSOCKET] = CURL_SOCKET_BAD; /* no file descriptor */ conn->recv_idx = 0; /* default for receiving transfer data */ conn->send_idx = 0; /* default for sending transfer data */ conn->connection_id = -1; /* no ID */ conn->attached_xfers = 0; - - /* Remember time this connection started */ - conn->created = *pnow; + conn->shutdown.start_ms[FIRSTSOCKET] = + conn->shutdown.start_ms[SECONDARYSOCKET] = -1; #ifndef CURL_DISABLE_FTP conn->bits.ftp_use_epsv = data->set.ftp_use_epsv; diff --git a/tests/unit/unit2606.c b/tests/unit/unit2606.c index a4db5def4b68..2c9d83ae4490 100644 --- a/tests/unit/unit2606.c +++ b/tests/unit/unit2606.c @@ -131,6 +131,9 @@ static void t2606_run(struct Curl_easy *data, const struct t2606_case *tc) abort_if(!conn, "could not allocate a connection"); conn->created = curlx_now(); + conn->shutdown.start_ms[FIRSTSOCKET] = + conn->shutdown.start_ms[SECONDARYSOCKET] = -1; + memset(&ctx0, 0, sizeof(ctx0)); memset(&ctx1, 0, sizeof(ctx1)); ctx0.sock = 42; From a226b5ea042831409b9d5899b170e835eb3dd960 Mon Sep 17 00:00:00 2001 From: Daniel Stenberg Date: Fri, 4 Sep 2026 10:08:36 +0200 Subject: [PATCH 033/481] CURLOPT*ISSUERCERT*: not verified cryptographically Reported-by: Stanislav Fort Closes #22825 --- docs/libcurl/opts/CURLOPT_ISSUERCERT.md | 7 +++++-- docs/libcurl/opts/CURLOPT_ISSUERCERT_BLOB.md | 12 +++++++----- docs/libcurl/opts/CURLOPT_PROXY_ISSUERCERT.md | 3 +++ docs/libcurl/opts/CURLOPT_PROXY_ISSUERCERT_BLOB.md | 11 +++++++---- 4 files changed, 22 insertions(+), 11 deletions(-) diff --git a/docs/libcurl/opts/CURLOPT_ISSUERCERT.md b/docs/libcurl/opts/CURLOPT_ISSUERCERT.md index 1e10b1bbb297..a8dfb1d5206b 100644 --- a/docs/libcurl/opts/CURLOPT_ISSUERCERT.md +++ b/docs/libcurl/opts/CURLOPT_ISSUERCERT.md @@ -37,9 +37,12 @@ associated with the certificate provided by the option. This additional check is useful in multi-level PKI where one needs to enforce that the peer certificate is from a specific branch of the tree. +The provided issuer certificate is used only for an issuer check against the +peer certificate; it is not validated or trusted by itself. + This option makes sense only when used in combination with the -CURLOPT_SSL_VERIFYPEER(3) option. Otherwise, the result of the check is -not considered as failure. +CURLOPT_SSL_VERIFYPEER(3) option. Otherwise, the result of the check is not +considered as failure. A specific error code (CURLE_SSL_ISSUER_ERROR) is defined with the option, which is returned if the setup of the SSL/TLS session has failed due to a diff --git a/docs/libcurl/opts/CURLOPT_ISSUERCERT_BLOB.md b/docs/libcurl/opts/CURLOPT_ISSUERCERT_BLOB.md index c0003536196a..3554d622543c 100644 --- a/docs/libcurl/opts/CURLOPT_ISSUERCERT_BLOB.md +++ b/docs/libcurl/opts/CURLOPT_ISSUERCERT_BLOB.md @@ -39,14 +39,16 @@ certificate provided by the option. This additional check is useful in multi-level PKI where one needs to enforce that the peer certificate is from a specific branch of the tree. -This option should be used in combination with the -CURLOPT_SSL_VERIFYPEER(3) option. Otherwise, the result of the check is -not considered as failure. +The provided issuer certificate is used only for an issuer check against the +peer certificate; it is not validated or trusted by itself. + +This option should be used in combination with the CURLOPT_SSL_VERIFYPEER(3) +option. Otherwise, the result of the check is not considered as failure. A specific error code (CURLE_SSL_ISSUER_ERROR) is defined with the option, which is returned if the setup of the SSL/TLS session has failed due to a -mismatch with the issuer of peer certificate (CURLOPT_SSL_VERIFYPEER(3) -has to be set too for the check to fail). +mismatch with the issuer of peer certificate (CURLOPT_SSL_VERIFYPEER(3) has to +be set too for the check to fail). If the blob is initialized with the flags member of struct curl_blob set to CURL_BLOB_COPY, the application does not have to keep the buffer around after diff --git a/docs/libcurl/opts/CURLOPT_PROXY_ISSUERCERT.md b/docs/libcurl/opts/CURLOPT_PROXY_ISSUERCERT.md index c3687a46fe87..61d204e5c4c7 100644 --- a/docs/libcurl/opts/CURLOPT_PROXY_ISSUERCERT.md +++ b/docs/libcurl/opts/CURLOPT_PROXY_ISSUERCERT.md @@ -39,6 +39,9 @@ indeed the one associated with the certificate provided by the option. This additional check is useful in multi-level PKI where one needs to enforce that the peer certificate is from a specific branch of the tree. +The provided issuer certificate is used only for an issuer check against the +peer certificate; it is not validated or trusted by itself. + This option makes sense only when used in combination with the CURLOPT_PROXY_SSL_VERIFYPEER(3) option. Otherwise, the result of the check is not considered as failure. diff --git a/docs/libcurl/opts/CURLOPT_PROXY_ISSUERCERT_BLOB.md b/docs/libcurl/opts/CURLOPT_PROXY_ISSUERCERT_BLOB.md index 0885ad96e0c9..018d0ea62106 100644 --- a/docs/libcurl/opts/CURLOPT_PROXY_ISSUERCERT_BLOB.md +++ b/docs/libcurl/opts/CURLOPT_PROXY_ISSUERCERT_BLOB.md @@ -40,14 +40,17 @@ associated with the certificate provided by the option. This additional check is useful in multi-level PKI where one needs to enforce that the peer certificate is from a specific branch of the tree. +The provided issuer certificate is used only for an issuer check against the +peer certificate; it is not validated or trusted by itself. + This option should be used in combination with the -CURLOPT_PROXY_SSL_VERIFYPEER(3) option. Otherwise, the result of the -check is not considered as failure. +CURLOPT_PROXY_SSL_VERIFYPEER(3) option. Otherwise, the result of the check is +not considered as failure. A specific error code (CURLE_SSL_ISSUER_ERROR) is defined with the option, which is returned if the setup of the SSL/TLS session has failed due to a -mismatch with the issuer of peer certificate -(CURLOPT_PROXY_SSL_VERIFYPEER(3) has to be set too for the check to fail). +mismatch with the issuer of peer certificate (CURLOPT_PROXY_SSL_VERIFYPEER(3) +has to be set too for the check to fail). If the blob is initialized with the flags member of struct curl_blob set to CURL_BLOB_COPY, the application does not have to keep the buffer around after From f042c25fb876d0da5ff5f35332bcc3a8b5a7fe66 Mon Sep 17 00:00:00 2001 From: Joshua Rogers Date: Fri, 4 Sep 2026 10:16:41 +0200 Subject: [PATCH 034/481] openssl/gtls: reject CRLfile with native CA store, matching rustls Closes #22826 --- lib/vtls/gtls.c | 7 +++++++ lib/vtls/openssl.c | 7 +++++++ 2 files changed, 14 insertions(+) diff --git a/lib/vtls/gtls.c b/lib/vtls/gtls.c index 4e4d42ec6d6d..2b85d4a7801e 100644 --- a/lib/vtls/gtls.c +++ b/lib/vtls/gtls.c @@ -541,6 +541,13 @@ static CURLcode gtls_populate_creds(struct Curl_cfilter *cf, if(creds_are_empty) infof(data, " no trust anchors configured"); +#ifdef USE_APPLE_SECTRUST + if(config->CRLfile && config->native_ca_store) { + failf(data, "gnutls: CRL file not supported with native CA store; " + "the platform verifier has no CRL attachment API"); + return CURLE_NOT_BUILT_IN; + } +#endif if(config->CRLfile) { /* set the CRL list file */ rc = gnutls_certificate_set_x509_crl_file(creds, config->CRLfile, diff --git a/lib/vtls/openssl.c b/lib/vtls/openssl.c index fed1af1bede5..145d228dd567 100644 --- a/lib/vtls/openssl.c +++ b/lib/vtls/openssl.c @@ -3105,6 +3105,13 @@ static CURLcode ossl_populate_x509_store(struct Curl_cfilter *cf, return result; /* Does not make sense to load a CRL file without peer verification */ +#ifdef USE_APPLE_SECTRUST + if(ssl_crlfile && conn_config->native_ca_store) { + failf(data, "openssl: CRL file not supported with native CA store; " + "the platform verifier has no CRL attachment API"); + return CURLE_NOT_BUILT_IN; + } +#endif if(ssl_crlfile) { /* tell OpenSSL where to find CRL file that is used to check certificate * revocation */ From c53420e42742a68004854a7fa354f9500b4b1516 Mon Sep 17 00:00:00 2001 From: Rudolf Polzer Date: Fri, 4 Sep 2026 00:48:12 -0700 Subject: [PATCH 035/481] ossl_verifyhost: remove assumption of null termination of ASN1_STRING. This only affects hosts where the deprecated CN is used for matching DNS names, instead of the modern way, which is by using a dNSName-typed SAN. A similar change but for certs that store the hostname in the SAN (as they should nowadays) was in 543adc4de024dda4f3b8f368893c7d01a3cf0ea8. Fixes #22822 Closes #22824 --- lib/vtls/openssl.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/vtls/openssl.c b/lib/vtls/openssl.c index 145d228dd567..d43f1bdc9bbc 100644 --- a/lib/vtls/openssl.c +++ b/lib/vtls/openssl.c @@ -2166,7 +2166,7 @@ static CURLcode ossl_verifyhost(struct Curl_easy *data, if((cnlen <= 0) || !cn) result = CURLE_OUT_OF_MEMORY; - else if((size_t)cnlen != strlen((char *)cn)) { + else if(memchr(cn, '\0', cnlen)) { /* there was a null-terminator before the end of string, this cannot match and we return failure! */ failf(data, "SSL: illegal cert name field"); @@ -2184,12 +2184,12 @@ static CURLcode ossl_verifyhost(struct Curl_easy *data, } else if(!Curl_cert_hostcheck((const char *)cn, cnlen, peer->origin->hostname, hostlen)) { - failf(data, "SSL: certificate subject name '%s' does not match " - "target hostname '%s'", cn, peer->origin->user_hostname); + failf(data, "SSL: certificate subject name '%.*s' does not match " + "target hostname '%s'", cnlen, cn, peer->origin->user_hostname); result = CURLE_PEER_FAILED_VERIFICATION; } else { - infof(data, " common name: %s (matched)", cn); + infof(data, " common name: %.*s (matched)", cnlen, cn); } if(free_cn) OPENSSL_free(cn); From eab7d91ade03c58e612b95055899aa12c2d87443 Mon Sep 17 00:00:00 2001 From: Max Dymond Date: Wed, 2 Sep 2026 18:24:18 +0100 Subject: [PATCH 036/481] multi: reduce struct size with small cleanups Use byte-sized multi API call-stack fields, remove the unused uint32 table destructor and replace the last timeout value with a bit. This reduces Curl_multi by 32 bytes on 64-bit builds. Closes #22807 --- docs/internals/UINT_SETS.md | 2 +- lib/api.c | 15 ++-- lib/api.h | 8 +-- lib/multi.c | 9 ++- lib/multihandle.h | 2 +- lib/multiif.h | 7 +- lib/uint-table.c | 6 +- lib/uint-table.h | 13 +--- tests/data/Makefile.am | 1 + tests/data/test3230 | 19 +++++ tests/unit/Makefile.inc | 1 + tests/unit/unit3212.c | 2 +- tests/unit/unit3230.c | 140 ++++++++++++++++++++++++++++++++++++ 13 files changed, 190 insertions(+), 35 deletions(-) create mode 100644 tests/data/test3230 create mode 100644 tests/unit/unit3230.c diff --git a/docs/internals/UINT_SETS.md b/docs/internals/UINT_SETS.md index 963cc82d48fe..22ba858c6105 100644 --- a/docs/internals/UINT_SETS.md +++ b/docs/internals/UINT_SETS.md @@ -51,7 +51,7 @@ This iteration has the following properties: ### Memory For storing 1000 entries, the table would allocate one block of 8KB on -a 64-bit system, plus the 2 pointers and 3 `uint32_t` in its base `struct +a 64-bit system, plus the pointer and 3 `uint32_t` in its base `struct uint32_tbl`. A resize allocates a completely new pointer array, copy the existing entries and free the previous one. diff --git a/lib/api.c b/lib/api.c index 15e3789861ab..1636d006d275 100644 --- a/lib/api.c +++ b/lib/api.c @@ -29,6 +29,13 @@ #include "multiif.h" #include "vtls/vtls_scache.h" +typedef char mapi_fn_ids_fit_uint8[ + (CURL_MAPI_FN_LAST <= CURL_CBAPI_FN_START) ? 1 : -1]; +typedef char cbapi_fn_ids_fit_uint8[ + ((CURL_CBAPI_FN_LAST - 1) <= UINT8_MAX) ? 1 : -1]; +typedef char mapi_depth_fits_uint8[ + (CURL_MAPI_MAX_RECURSION <= UINT8_MAX) ? 1 : -1]; + struct Curl_eapi_fn_props { Curl_eapi_fn fn; uint8_t data_is_killed; /* easy handle is killed in call */ @@ -135,7 +142,7 @@ static bool eapi_in_event_cb(struct Curl_easy *data) size_t i; for(i = 0; i < multi->callstack.count; ++i) { if(multi->callstack.calls[i] >= CURL_CBAPI_FN_START) { - uint16_t fn = multi->callstack.calls[i]; + uint8_t fn = multi->callstack.calls[i]; if((fn < CURL_CBAPI_FN_LAST) && cbapi_fn_props[fn - CURL_CBAPI_FN_START].is_event_cb) return TRUE; @@ -376,7 +383,7 @@ bool Curl_mapi_enter(struct Curl_mapi_guard *guard, mresult = CURLM_RECURSIVE_API_CALL; goto out; } - multi->callstack.calls[multi->callstack.count] = (uint16_t)fn; + multi->callstack.calls[multi->callstack.count] = (uint8_t)fn; ++multi->callstack.count; guard->depth = multi->callstack.count; guard->multi = fn_props->multi_is_killed ? NULL : multi; @@ -400,7 +407,7 @@ void Curl_mapi_leave(struct Curl_mapi_guard *guard) DEBUGASSERT(0); /* someone forgot to clean up */ } /* reset to depth the guard was entered in */ - guard->multi->callstack.count = (uint16_t)(guard->depth - 1); + guard->multi->callstack.count = (uint8_t)(guard->depth - 1); } } } @@ -439,7 +446,7 @@ void Curl_cbapi_enter(struct Curl_mapi_guard *guard, /* if multi callstack already at max depth, leave */ if(multi->callstack.count >= CURL_MAPI_MAX_RECURSION) return; - multi->callstack.calls[multi->callstack.count] = (uint16_t)fn; + multi->callstack.calls[multi->callstack.count] = (uint8_t)fn; ++multi->callstack.count; guard->depth = multi->callstack.count; guard->multi = multi; diff --git a/lib/api.h b/lib/api.h index 337d686bb5b9..b87f5b99cb92 100644 --- a/lib/api.h +++ b/lib/api.h @@ -99,7 +99,7 @@ typedef enum { CURL_MAPI_FN_LAST } Curl_mapi_fn; -#define CURL_CBAPI_FN_START (16 * 1024) +#define CURL_CBAPI_FN_START 128 /* the callback functions */ typedef enum { @@ -169,13 +169,13 @@ CURLHcode Curl_eapi_hcode(CURLcode result); #define CURL_MAPI_MAX_RECURSION 15 struct Curl_mapi_stack { - uint16_t count; - uint16_t calls[CURL_MAPI_MAX_RECURSION]; + uint8_t count; + uint8_t calls[CURL_MAPI_MAX_RECURSION]; }; struct Curl_mapi_guard { struct Curl_multi *multi; /* != NULL if handle stays */ - uint16_t depth; /* > 0 if this guard was entered */ + uint8_t depth; /* > 0 if this guard was entered */ }; bool Curl_mapi_enter(struct Curl_mapi_guard *guard, diff --git a/lib/multi.c b/lib/multi.c index ae7a37509ff8..25b5c4447bfd 100644 --- a/lib/multi.c +++ b/lib/multi.c @@ -254,7 +254,7 @@ struct Curl_multi *Curl_multi_handle(uint32_t xfer_table_size, Curl_dnscache_init(&multi->dnscache, dnssize); Curl_mntfy_init(multi); Curl_multi_ev_init(multi, ev_hashsize); - Curl_uint32_tbl_init(&multi->xfers, NULL); + Curl_uint32_tbl_init(&multi->xfers); Curl_uint32_bset_init(&multi->process); Curl_uint32_bset_init(&multi->dirty); Curl_uint32_bset_init(&multi->pending); @@ -264,7 +264,6 @@ struct Curl_multi *Curl_multi_handle(uint32_t xfer_table_size, multi->multiplexing = TRUE; multi->max_concurrent_streams = 100; - multi->last_timeout_ms = -1; #ifdef ENABLE_WAKEUP multi->wakeup_pair[0] = CURL_SOCKET_BAD; multi->wakeup_pair[1] = CURL_SOCKET_BAD; @@ -3575,7 +3574,7 @@ CURLMcode Curl_update_timer(struct Curl_multi *multi) return CURLM_OK; multi_timeout(multi, &timeouts_offset_us, &timeout_ms); - if(timeout_ms < 0 && multi->last_timeout_ms < 0) { + if(timeout_ms < 0 && !multi->last_timeout_set) { /* nothing to do */ } else if(timeout_ms < 0) { @@ -3584,7 +3583,7 @@ CURLMcode Curl_update_timer(struct Curl_multi *multi) timeout_ms = -1; /* normalize */ set_value = TRUE; } - else if(multi->last_timeout_ms < 0) { + else if(!multi->last_timeout_set) { CURL_TRC_M(multi->admin, "[TIMER] set %dms, none before", timeout_ms); set_value = TRUE; } @@ -3606,7 +3605,7 @@ CURLMcode Curl_update_timer(struct Curl_multi *multi) struct Curl_mapi_guard guard; multi->last_expire_offset_us = timeouts_offset_us; - multi->last_timeout_ms = timeout_ms; + multi->last_timeout_set = timeout_ms >= 0; CURL_CBAPI_MULTI_START(&guard, multi, multi_timer_cb); rc = multi->timer_cb(multi, timeout_ms, multi->timer_userp); CURL_CBAPI_MULTI_END(&guard); diff --git a/lib/multihandle.h b/lib/multihandle.h index 7f56ff2bf0d6..0073496a7503 100644 --- a/lib/multihandle.h +++ b/lib/multihandle.h @@ -158,7 +158,6 @@ struct Curl_multi { /* timer callback and user data pointer for the *socket() API */ curl_multi_timer_callback timer_cb; void *timer_userp; - timediff_t last_timeout_ms; /* the last timeout value set via timer_cb */ #ifdef USE_WINSOCK WSAEVENT wsa_event; /* Winsock event used for waits */ @@ -191,6 +190,7 @@ struct Curl_multi { #endif BIT(dead); /* a callback returned error, everything needs to crash and burn */ + BIT(last_timeout_set); /* last timer callback value was nonnegative */ BIT(xfer_buf_borrowed); /* xfer_buf is currently being borrowed */ BIT(xfer_ulbuf_borrowed); /* xfer_ulbuf is currently being borrowed */ BIT(xfer_sockbuf_borrowed); /* xfer_sockbuf is currently being borrowed */ diff --git a/lib/multiif.h b/lib/multiif.h index 538933f1c415..f36a3fec618a 100644 --- a/lib/multiif.h +++ b/lib/multiif.h @@ -89,7 +89,7 @@ CURLMcode Curl_multi_pollset(struct Curl_easy *data, * @param pbuf on return, the buffer to use or NULL on error * @param pbuflen on return, the size of *pbuf or 0 on error * @return CURLE_OK when buffer is available and is returned. - * CURLE_OUT_OF_MEMORy on failure to allocate the buffer, + * CURLE_OUT_OF_MEMORY on failure to allocate the buffer, * CURLE_FAILED_INIT if the easy handle is without multi. * CURLE_AGAIN if the buffer is borrowed already. */ @@ -113,7 +113,7 @@ void Curl_multi_xfer_buf_release(struct Curl_easy *data, char *buf); * @param pbuf on return, the buffer to use or NULL on error * @param pbuflen on return, the size of *pbuf or 0 on error * @return CURLE_OK when buffer is available and is returned. - * CURLE_OUT_OF_MEMORy on failure to allocate the buffer, + * CURLE_OUT_OF_MEMORY on failure to allocate the buffer, * CURLE_FAILED_INIT if the easy handle is without multi. * CURLE_AGAIN if the buffer is borrowed already. */ @@ -138,8 +138,7 @@ void Curl_multi_xfer_ulbuf_release(struct Curl_easy *data, char *buf); * @param blen requested length of the buffer * @param pbuf on return, the buffer to use or NULL on error * @return CURLE_OK when buffer is available and is returned. - * CURLE_OUT_OF_MEMORy on failure to allocate the buffer, - * CURLE_FAILED_INIT if the easy handle is without multi. + * CURLE_OUT_OF_MEMORY on failure to allocate the buffer, * CURLE_AGAIN if the buffer is borrowed already. */ CURLcode Curl_multi_xfer_sockbuf_borrow(struct Curl_easy *data, diff --git a/lib/uint-table.c b/lib/uint-table.c index addd2c9cb2e1..50427a891a09 100644 --- a/lib/uint-table.c +++ b/lib/uint-table.c @@ -29,11 +29,9 @@ #define CURL_UINT32_TBL_MAGIC 0x62757473 #endif -void Curl_uint32_tbl_init(struct uint32_tbl *tbl, - Curl_uint32_tbl_entry_dtor *entry_dtor) +void Curl_uint32_tbl_init(struct uint32_tbl *tbl) { memset(tbl, 0, sizeof(*tbl)); - tbl->entry_dtor = entry_dtor; tbl->last_key_added = UINT32_MAX; #ifdef DEBUGBUILD tbl->init = CURL_UINT32_TBL_MAGIC; @@ -49,8 +47,6 @@ static void uint32_tbl_clear_rows(struct uint32_tbl *tbl, end = CURLMIN(upto_excluding, tbl->nrows); for(i = from; i < end; ++i) { if(tbl->rows[i]) { - if(tbl->entry_dtor) - tbl->entry_dtor(i, tbl->rows[i]); tbl->rows[i] = NULL; tbl->nentries--; } diff --git a/lib/uint-table.h b/lib/uint-table.h index 398a26a64843..743d13b72ce4 100644 --- a/lib/uint-table.h +++ b/lib/uint-table.h @@ -25,12 +25,8 @@ ***************************************************************************/ #include "curl_setup.h" -/* Destructor for a single table entry */ -typedef void Curl_uint32_tbl_entry_dtor(uint32_t key, void *entry); - struct uint32_tbl { void **rows; /* array of void* holding entries */ - Curl_uint32_tbl_entry_dtor *entry_dtor; uint32_t nrows; /* length of `rows` array */ uint32_t nentries; /* entries in table */ uint32_t last_key_added; /* UINT_MAX or last key added */ @@ -39,17 +35,14 @@ struct uint32_tbl { #endif }; -/* Initialize the table with 0 capacity. - * The optional `entry_dtor` is called when a table entry is removed, - * Passing NULL means no action is taken on removal. */ -void Curl_uint32_tbl_init(struct uint32_tbl *tbl, - Curl_uint32_tbl_entry_dtor *entry_dtor); +/* Initialize the table with 0 capacity. */ +void Curl_uint32_tbl_init(struct uint32_tbl *tbl); /* Resize the table to change capacity `nmax`. When `nmax` is reduced, * all present entries with key equal or larger to `nmax` are removed. */ CURLcode Curl_uint32_tbl_resize(struct uint32_tbl *tbl, uint32_t nrows); -/* Destroy the table, freeing all entries. */ +/* Destroy the table, freeing its storage. */ void Curl_uint32_tbl_destroy(struct uint32_tbl *tbl); /* Get the table capacity. */ diff --git a/tests/data/Makefile.am b/tests/data/Makefile.am index add0dccd775d..74d8d60e4d66 100644 --- a/tests/data/Makefile.am +++ b/tests/data/Makefile.am @@ -2081,6 +2081,7 @@ TESTCASES = \ test3227 \ test3228 \ test3229 \ + test3230 \ \ test3300 \ test3301 \ diff --git a/tests/data/test3230 b/tests/data/test3230 new file mode 100644 index 000000000000..79bed0f5ad2e --- /dev/null +++ b/tests/data/test3230 @@ -0,0 +1,19 @@ + + + + +unittest +multi + + + +# Client-side + + +unittest + + +multi timer unit tests + + + diff --git a/tests/unit/Makefile.inc b/tests/unit/Makefile.inc index e0effbcb9af2..c393c74e33e8 100644 --- a/tests/unit/Makefile.inc +++ b/tests/unit/Makefile.inc @@ -102,6 +102,7 @@ TESTS_C = \ unit3216.c \ unit3219.c \ unit3227.c \ + unit3230.c \ unit3300.c \ unit3301.c \ unit3302.c \ diff --git a/tests/unit/unit3212.c b/tests/unit/unit3212.c index 26a22097212a..a30830b0e94f 100644 --- a/tests/unit/unit3212.c +++ b/tests/unit/unit3212.c @@ -30,7 +30,7 @@ static CURLcode t3212_setup(struct uint32_tbl *tbl) { - Curl_uint32_tbl_init(tbl, NULL); + Curl_uint32_tbl_init(tbl); return Curl_uint32_tbl_resize(tbl, TBL_SIZE); } diff --git a/tests/unit/unit3230.c b/tests/unit/unit3230.c new file mode 100644 index 000000000000..4d2d72e55573 --- /dev/null +++ b/tests/unit/unit3230.c @@ -0,0 +1,140 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ +#include "unitcheck.h" +#include "urldata.h" +#include "multiif.h" + +#define T3230_TIMER_CALLS 6 + +struct t3230_timer_ctx { + long timeout_ms[T3230_TIMER_CALLS]; + size_t count; +}; + +static int t3230_timer_cb(CURLM *multi, long timeout_ms, void *userp) +{ + struct t3230_timer_ctx *ctx = userp; + (void)multi; + + if(ctx->count < CURL_ARRAYSIZE(ctx->timeout_ms)) + ctx->timeout_ms[ctx->count] = timeout_ms; + ++ctx->count; + return 0; +} + +static CURLcode t3230_setup(void) +{ + CURLcode result = CURLE_OK; + global_init(CURL_GLOBAL_ALL); + return result; +} + +static void t3230_test_timer_updates(void) +{ + CURLM *handle = curl_multi_init(); + struct Curl_multi *multi = handle; + struct t3230_timer_ctx ctx; + struct curltime now; + timediff_t expire_offset_us; + CURLMcode mresult; + int running; + + memset(&ctx, 0, sizeof(ctx)); + if(!multi) { + fail("multi handle creation failed"); + return; + } + + mresult = curl_multi_setopt(handle, CURLMOPT_TIMERDATA, &ctx); + fail_unless(mresult == CURLM_OK, "setting timer data failed"); + mresult = curl_multi_setopt(handle, CURLMOPT_TIMERFUNCTION, + t3230_timer_cb); + fail_unless(mresult == CURLM_OK, "setting timer callback failed"); + + fail_unless(Curl_update_timer(multi) == CURLM_OK, + "initial timer update failed"); + fail_unless(!ctx.count, "callback invoked without a timeout"); + fail_unless(!multi->last_timeout_set, "initial timer marked as set"); + + now = curlx_now(); + Curl_expire_set(multi->admin, EXPIRE_TIMEOUT, 600000, &now); + fail_unless(Curl_update_timer(multi) == CURLM_OK, + "setting timer failed"); + fail_unless(ctx.count == 1, "setting timer did not invoke callback"); + fail_unless(multi->last_timeout_set, "timer not marked as set"); + + Curl_expire_set(multi->admin, EXPIRE_TIMEOUT, 1200000, &now); + fail_unless(Curl_update_timer(multi) == CURLM_OK, + "replacing timer failed"); + fail_unless(ctx.count == 2, "replacing timer did not invoke callback"); + + fail_unless(Curl_update_timer(multi) == CURLM_OK, + "unchanged timer update failed"); + fail_unless(ctx.count == 2, "unchanged timer invoked callback"); + + expire_offset_us = multi->last_expire_offset_us; + mresult = curl_multi_socket_action(handle, CURL_SOCKET_TIMEOUT, 0, &running); + fail_unless(mresult == CURLM_OK, "forced timer refresh failed"); + fail_unless(ctx.count == 3, "timer refresh did not invoke callback"); + fail_unless(multi->last_expire_offset_us == expire_offset_us, + "timer refresh changed expiry offset"); + fail_unless(multi->last_timeout_set, "refreshed timer not marked as set"); + + Curl_expire_clear_all(multi->admin); + fail_unless(Curl_update_timer(multi) == CURLM_OK, + "timer clear update failed"); + fail_unless(ctx.count == 4, "clearing timer did not invoke callback"); + fail_unless(!multi->last_timeout_set, "cleared timer marked as set"); + fail_unless(Curl_update_timer(multi) == CURLM_OK, + "repeated timer clear update failed"); + + Curl_multi_mark_dirty(multi->admin); + fail_unless(Curl_update_timer(multi) == CURLM_OK, + "zero timer update failed"); + fail_unless(multi->last_timeout_set, "zero timer not marked as set"); + Curl_multi_clear_dirty(multi->admin); + fail_unless(Curl_update_timer(multi) == CURLM_OK, + "clearing zero timer failed"); + fail_unless(!multi->last_timeout_set, "zero timer clear left timer set"); + + fail_unless(ctx.count == T3230_TIMER_CALLS, "wrong timer callback count"); + fail_unless(ctx.timeout_ms[0] >= 0, "wrong initial timeout"); + fail_unless(ctx.timeout_ms[1] > ctx.timeout_ms[0], + "wrong replacement timeout"); + fail_unless(ctx.timeout_ms[2] >= 0, "wrong refreshed timeout"); + fail_unless(ctx.timeout_ms[3] == -1, "wrong cleared timeout"); + fail_unless(ctx.timeout_ms[4] == 0, "wrong zero timeout"); + fail_unless(ctx.timeout_ms[5] == -1, "wrong zero timeout clear"); + + curl_multi_cleanup(handle); +} + +static CURLcode test_unit3230(const char *arg) +{ + UNITTEST_BEGIN(t3230_setup()) + + t3230_test_timer_updates(); + + UNITTEST_END(curl_global_cleanup()) +} From ea90a974463196953b1863c850fd2f3329169abc Mon Sep 17 00:00:00 2001 From: Stefan Eissing Date: Fri, 4 Sep 2026 11:17:30 +0200 Subject: [PATCH 037/481] digest: check peer equality for staleness Check that current peer is *equal* from last peer's state information. This accomodates also scheme changes, which the previous check did not. Closes #22828 --- lib/http_digest.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/http_digest.c b/lib/http_digest.c index 1ec29bd391a1..4968e27c331f 100644 --- a/lib/http_digest.c +++ b/lib/http_digest.c @@ -41,7 +41,7 @@ static void digest_flush_stale(struct Curl_easy *data, struct Curl_creds *creds) { bool flush = FALSE; - if(digest->origin && !Curl_peer_same_destination(peer, digest->origin)) { + if(digest->origin && !Curl_peer_equal(peer, digest->origin)) { CURL_TRC_M(data, "http_digest, reset on peer change to %s:%u", peer->hostname, peer->port); flush = TRUE; From 779b84936b293d5983fd4d24b1472d59346fc6d5 Mon Sep 17 00:00:00 2001 From: Viktor Szakats Date: Mon, 31 Aug 2026 13:42:44 +0200 Subject: [PATCH 038/481] build: drop `HTTP_ONLY` option Drop `HTTP_ONLY` C macro for raw builds, and `HTTP_ONLY=ON` for CMake. It's a niche option that's been historically incomplete throughout its existence, also a maintenance pain to get it right and synced with protocols and with each other. Note there was no matching `./configure` option either. At the time of this commit, it failed to disable SCP/SFTP if CMake found libssh/libssh2 (the default), and C flags missed to disable IPFS in addition. Also: - drop from AppVeyor CI build mix. - GHA/macos: replace with individual disable options. Follow-up to 4c5307b45655ba75ab066564afdc0c111a8b9291 Follow-up to 08ef208fb78fb2eabc5cec08c23e74e251eac898 Closes #22744 Fixes #22740 Closes #22764 --- .github/workflows/macos.yml | 6 +++-- CMakeLists.txt | 22 ----------------- appveyor.yml | 4 ++-- docs/INSTALL-CMAKE.md | 1 - lib/curl_setup.h | 48 ------------------------------------- 5 files changed, 6 insertions(+), 75 deletions(-) diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index e44b276a2a98..c2441d5de81a 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -267,12 +267,14 @@ jobs: tflags: '--min=960' generate: >- -DENABLE_DEBUG=ON -DENABLE_ARES=ON - -DCURL_ENABLE_SSL=OFF -DHTTP_ONLY=ON - -DCURL_DISABLE_ALTSVC=ON -DENABLE_UNIX_SOCKETS=OFF + -DCURL_ENABLE_SSL=OFF -DCURL_DISABLE_ALTSVC=ON -DENABLE_UNIX_SOCKETS=OFF -DCURL_USE_LIBSSH2=OFF -DCURL_USE_LIBSSH=OFF -DUSE_NGHTTP2=OFF -DCURL_USE_GSSAPI=OFF -DUSE_LIBIDN2=OFF -DCURL_USE_LIBPSL=OFF -DCURL_BROTLI=OFF -DCURL_ZLIB=OFF -DCURL_ZSTD=OFF -DBUILD_STATIC_LIBS=ON -DBUILD_SHARED_LIBS=OFF + -DCURL_DISABLE_DICT=ON -DCURL_DISABLE_FILE=ON -DCURL_DISABLE_FTP=ON -DCURL_DISABLE_GOPHER=ON -DCURL_DISABLE_IMAP=ON + -DCURL_DISABLE_IPFS=ON -DCURL_DISABLE_LDAP=ON -DCURL_DISABLE_LDAPS=ON -DCURL_DISABLE_MQTT=ON -DCURL_DISABLE_POP3=ON + -DCURL_DISABLE_RTSP=ON -DCURL_DISABLE_SMTP=ON -DCURL_DISABLE_TELNET=ON -DCURL_DISABLE_TFTP=ON -DCURL_DISABLE_WEBSOCKETS=ON - name: 'LibreSSL !ldap +examples' compiler: clang diff --git a/CMakeLists.txt b/CMakeLists.txt index 693d93d01417..29f8c67cf41c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -534,28 +534,6 @@ if(CURL_DISABLE_HTTP) set(CURL_DISABLE_WEBSOCKETS ON) endif() -# Corresponds to HTTP_ONLY in lib/curl_setup.h -option(HTTP_ONLY "Disable all protocols except HTTP (This overrides all CURL_DISABLE_* options)" OFF) -mark_as_advanced(HTTP_ONLY) - -if(HTTP_ONLY) - set(CURL_DISABLE_DICT ON) - set(CURL_DISABLE_FILE ON) - set(CURL_DISABLE_FTP ON) - set(CURL_DISABLE_GOPHER ON) - set(CURL_DISABLE_IMAP ON) - set(CURL_DISABLE_IPFS ON) - set(CURL_DISABLE_LDAP ON) - set(CURL_DISABLE_LDAPS ON) - set(CURL_DISABLE_MQTT ON) - set(CURL_DISABLE_POP3 ON) - set(CURL_DISABLE_RTSP ON) - set(CURL_DISABLE_SMTP ON) - set(CURL_DISABLE_TELNET ON) - set(CURL_DISABLE_TFTP ON) - set(CURL_DISABLE_WEBSOCKETS ON) -endif() - if(WINDOWS_STORE) set(CURL_DISABLE_TELNET ON) # telnet code needs fixing to compile for UWP. endif() diff --git a/appveyor.yml b/appveyor.yml index 6366dff9a5ff..95c081d31540 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -107,10 +107,10 @@ environment: CMAKE_GENERATOR: 'Visual Studio 18 2026' CMAKE_GENERATE: '-A x64 -DBUILD_SHARED_LIBS=OFF -DCURL_USE_SCHANNEL=OFF' - - job_name: 'CM VS2022, Debug, x64, !ssl, Static, HTTP-only, Build-tests' + - job_name: 'CM VS2022, Debug, x64, !ssl, Static, Build-tests' APPVEYOR_BUILD_WORKER_IMAGE: 'Visual Studio 2022' CMAKE_GENERATOR: 'Visual Studio 17 2022' - CMAKE_GENERATE: '-A x64 -DBUILD_SHARED_LIBS=OFF -DCURL_USE_SCHANNEL=OFF -DHTTP_ONLY=ON' + CMAKE_GENERATE: '-A x64 -DBUILD_SHARED_LIBS=OFF -DCURL_USE_SCHANNEL=OFF' # VisualStudioSolution builds diff --git a/docs/INSTALL-CMAKE.md b/docs/INSTALL-CMAKE.md index 16a3580634ef..a16911ac13b1 100644 --- a/docs/INSTALL-CMAKE.md +++ b/docs/INSTALL-CMAKE.md @@ -309,7 +309,6 @@ target_link_libraries(my_target PRIVATE CURL::libcurl) - `CURL_DISABLE_TYPECHECK`: Disable curl_easy_setopt()/curl_easy_getinfo() type checking. Default: `OFF` - `CURL_DISABLE_VERBOSE_STRINGS`: Disable verbose strings. Default: `OFF` - `CURL_DISABLE_WEBSOCKETS`: Disable WebSocket. Default: `OFF` -- `HTTP_ONLY`: Disable all protocols except HTTP (This overrides all `CURL_DISABLE_*` options). Default: `OFF` ## Environment diff --git a/lib/curl_setup.h b/lib/curl_setup.h index 0af3a67b6e16..e059cc198373 100644 --- a/lib/curl_setup.h +++ b/lib/curl_setup.h @@ -220,54 +220,6 @@ # include #endif -/* - * Disable other protocols when http is the only one desired. - */ -#ifdef HTTP_ONLY -# ifndef CURL_DISABLE_DICT -# define CURL_DISABLE_DICT -# endif -# ifndef CURL_DISABLE_FILE -# define CURL_DISABLE_FILE -# endif -# ifndef CURL_DISABLE_FTP -# define CURL_DISABLE_FTP -# endif -# ifndef CURL_DISABLE_GOPHER -# define CURL_DISABLE_GOPHER -# endif -# ifndef CURL_DISABLE_IMAP -# define CURL_DISABLE_IMAP -# endif -# ifndef CURL_DISABLE_LDAP -# define CURL_DISABLE_LDAP -# endif -# ifndef CURL_DISABLE_LDAPS -# define CURL_DISABLE_LDAPS -# endif -# ifndef CURL_DISABLE_MQTT -# define CURL_DISABLE_MQTT -# endif -# ifndef CURL_DISABLE_POP3 -# define CURL_DISABLE_POP3 -# endif -# ifndef CURL_DISABLE_RTSP -# define CURL_DISABLE_RTSP -# endif -# ifndef CURL_DISABLE_SMTP -# define CURL_DISABLE_SMTP -# endif -# ifndef CURL_DISABLE_TELNET -# define CURL_DISABLE_TELNET -# endif -# ifndef CURL_DISABLE_TFTP -# define CURL_DISABLE_TFTP -# endif -# ifndef CURL_DISABLE_WEBSOCKETS -# define CURL_DISABLE_WEBSOCKETS -# endif -#endif - /* * When HTTP is disabled, disable HTTP-only features */ From 020bc8dfbaefa9fa0b61e0993a6463b011598ab3 Mon Sep 17 00:00:00 2001 From: Martin Dukek Date: Fri, 4 Sep 2026 12:26:36 +0200 Subject: [PATCH 039/481] digest: fix maximum length quoted value parsing Separate the scan limit from the content buffer limit so maximum length Digest quoted values can be parsed correctly. Closes #22830 --- lib/vauth/digest.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/lib/vauth/digest.c b/lib/vauth/digest.c index 748c0d834726..abd1300bb5ff 100644 --- a/lib/vauth/digest.c +++ b/lib/vauth/digest.c @@ -61,6 +61,7 @@ bool Curl_auth_digest_get_pair(const char *str, char *value, char *content, const char **endptr) { int c; + size_t content_length = 0; bool starts_with_quote = FALSE; bool escape = FALSE; @@ -78,7 +79,7 @@ bool Curl_auth_digest_get_pair(const char *str, char *value, char *content, starts_with_quote = TRUE; } - for(c = DIGEST_MAX_CONTENT_LENGTH - 1; *str && c--; str++) { + for(c = DIGEST_MAX_CONTENT_LENGTH; *str && c--; str++) { if(!escape) { switch(*str) { case '\\': @@ -117,9 +118,16 @@ bool Curl_auth_digest_get_pair(const char *str, char *value, char *content, } } + if(content_length >= DIGEST_MAX_CONTENT_LENGTH - 1) { + /* no room for this byte plus the terminator */ + return FALSE; + } + escape = FALSE; *content++ = *str; + content_length++; } + if(escape) return FALSE; /* No character after backslash */ From f89eca9cd6e9a751345d50200572f00e1ce02c78 Mon Sep 17 00:00:00 2001 From: Daniel Stenberg Date: Tue, 1 Sep 2026 18:21:37 +0200 Subject: [PATCH 040/481] tool_cb_wrt: inject the no-clobber number before the extension Starting in curl 8.23.0, if the target file name uses an extension, curl instead creates the new file name by inserting `-[num]` before the extenstion. Downloading to `image.jpg` with --no-clobber, curl first tries to save it as `image-1.jpg` then `image-2.jpg` etc. Closes #22787 --- docs/cmdline-opts/no-clobber.md | 5 +++ src/tool_cb_wrt.c | 37 ++++++++++++++++++++-- tests/data/Makefile.am | 2 +- tests/data/test4644 | 56 +++++++++++++++++++++++++++++++++ 4 files changed, 97 insertions(+), 3 deletions(-) create mode 100644 tests/data/test4644 diff --git a/docs/cmdline-opts/no-clobber.md b/docs/cmdline-opts/no-clobber.md index f7b08855ef31..9225e2455128 100644 --- a/docs/cmdline-opts/no-clobber.md +++ b/docs/cmdline-opts/no-clobber.md @@ -21,6 +21,11 @@ that already exist. Instead, a dot and a number gets appended to the name of the file that would be created, up to filename.9999 after which it does not create any file. +Starting in curl 8.23.0, if the target filename uses an extension, curl +instead creates the new filename by inserting `-[num]` before the extension. +Downloading to `image.jpg` with --no-clobber, curl first tries to save it as +`image-1.jpg` then `image-2.jpg` etc. + Note that this is the negated option name documented. You can thus use --clobber to enforce the clobbering, even if --remote-header-name is specified. diff --git a/src/tool_cb_wrt.c b/src/tool_cb_wrt.c index 211fb521a4f7..6d18993bf48a 100644 --- a/src/tool_cb_wrt.c +++ b/src/tool_cb_wrt.c @@ -36,6 +36,40 @@ #define MAX_CLOBBER_ATTEMPTS 9999 +/* 'fname' contains [path + ] file name */ +static bool clobber_name(struct dynbuf *fbuf, const char *fname, + int next_num) +{ + /* skip the directory, if present */ + const char *slash = strrchr(fname, '/'); + const char *base = fname; + const char *dot; +#ifdef _WIN32 + const char *bslash = strrchr(fname, '\\'); + /* use the one furthest away */ + if(bslash && (!slash || (bslash > slash))) + slash = bslash; +#endif + if(slash) + base = slash + 1; + + dot = strrchr(base, '.'); + curlx_dyn_reset(fbuf); + if(dot && (dot != base) && dot[1]) { + /* there is an extension after the dot and the dot is not the first file + name character */ + int prefix = (int)(dot - fname); + if(curlx_dyn_addf(fbuf, "%.*s-%d.%s", prefix, fname, next_num, dot + 1)) + return FALSE; + } + else { + /* without any extension */ + if(curlx_dyn_addf(fbuf, "%s.%d", fname, next_num)) + return FALSE; + } + return TRUE; +} + /* create/open a local file for writing, return TRUE on success */ bool tool_create_output_file(struct OutStruct *outs, struct OperationConfig *config) @@ -82,8 +116,7 @@ bool tool_create_output_file(struct OutStruct *outs, (errno == EEXIST || errno == EISDIR) && /* and we have not reached the retry limit */ (next_num < max_num)) { - curlx_dyn_reset(&fbuffer); - if(curlx_dyn_addf(&fbuffer, "%s.%d", fname, next_num)) + if(!clobber_name(&fbuffer, fname, next_num)) return FALSE; next_num++; do { diff --git a/tests/data/Makefile.am b/tests/data/Makefile.am index 74d8d60e4d66..f9aa384f8ebd 100644 --- a/tests/data/Makefile.am +++ b/tests/data/Makefile.am @@ -2096,7 +2096,7 @@ TESTCASES = \ \ test4000 \ test4001 \ - \ + test4644 \ test5000 \ test5001 \ test5002 \ diff --git a/tests/data/test4644 b/tests/data/test4644 new file mode 100644 index 000000000000..4a708a22b74e --- /dev/null +++ b/tests/data/test4644 @@ -0,0 +1,56 @@ + + + + +--no-clobber + + + + + +HTTP/1.1 200 OK swsclose +Content-Length: 12 +Connection: close +Content-Type: text/html + +new content + + + + + +exists before command runs + + +exists before command runs + + +http + + +--no-clobber with extension and an added number + + +http://%HOSTIP:%HTTPPORT/%TESTNUMBER -o %LOGDIR/save.txt --no-clobber + + + +# Verify data after the test has been "shot". + + +GET /%TESTNUMBER HTTP/1.1 +Host: %HOSTIP:%HTTPPORT +User-Agent: curl/%VERSION +Accept: */* + + + +# this file should be untouched + +exists before command runs + + +new content + + + From 9be5c6849225f2dbb3bf60249998c33575dd0910 Mon Sep 17 00:00:00 2001 From: Daniel Stenberg Date: Fri, 4 Sep 2026 14:04:53 +0200 Subject: [PATCH 041/481] digest: parse without storing 'value' in local buffer Use of curlx_str_ functions avoids having a copy. Closes #22831 --- lib/vauth/digest.c | 45 +++++++++++++++++------------------------ lib/vauth/digest.h | 4 ++-- lib/vauth/digest_sspi.c | 22 +++++++++----------- 3 files changed, 31 insertions(+), 40 deletions(-) diff --git a/lib/vauth/digest.c b/lib/vauth/digest.c index abd1300bb5ff..1903ffd07aef 100644 --- a/lib/vauth/digest.c +++ b/lib/vauth/digest.c @@ -57,19 +57,16 @@ #define DIGEST_QOP_VALUE_STRING_AUTH_CONF "auth-conf" #endif -bool Curl_auth_digest_get_pair(const char *str, char *value, char *content, - const char **endptr) +bool Curl_auth_digest_get_pair(const char *str, struct Curl_str *value, + char *content, const char **endptr) { int c; size_t content_length = 0; bool starts_with_quote = FALSE; bool escape = FALSE; - for(c = DIGEST_MAX_VALUE_LENGTH - 1; (*str && (*str != '=') && c--);) - *value++ = *str++; - *value = 0; - - if('=' != *str++) + if(curlx_str_until(&str, value, DIGEST_MAX_VALUE_LENGTH, '=') || + curlx_str_single(&str, '=')) /* eek, no match */ return FALSE; @@ -538,47 +535,46 @@ CURLcode Curl_auth_decode_digest_http_message(const char *chlg, Curl_auth_digest_cleanup(digest); for(;;) { - char value[DIGEST_MAX_VALUE_LENGTH]; + struct Curl_str value; char content[DIGEST_MAX_CONTENT_LENGTH]; /* Pass all additional spaces here */ - while(*chlg && ISBLANK(*chlg)) - chlg++; + curlx_str_passblanks(&chlg); /* Extract a value=content pair */ - if(Curl_auth_digest_get_pair(chlg, value, content, &chlg)) { - if(curl_strequal(value, "nonce")) { + if(Curl_auth_digest_get_pair(chlg, &value, content, &chlg)) { + if(curlx_str_casecompare(&value, "nonce")) { curlx_free(digest->nonce); digest->nonce = curlx_strdup(content); if(!digest->nonce) return CURLE_OUT_OF_MEMORY; } - else if(curl_strequal(value, "stale")) { + else if(curlx_str_casecompare(&value, "stale")) { if(curl_strequal(content, "true")) { digest->stale = TRUE; digest->nc = 1; /* we make a new nonce now */ } } - else if(curl_strequal(value, "realm")) { + else if(curlx_str_casecompare(&value, "realm")) { curlx_free(digest->realm); digest->realm = curlx_strdup(content); if(!digest->realm) return CURLE_OUT_OF_MEMORY; } - else if(curl_strequal(value, "opaque")) { + else if(curlx_str_casecompare(&value, "opaque")) { curlx_free(digest->opaque); digest->opaque = curlx_strdup(content); if(!digest->opaque) return CURLE_OUT_OF_MEMORY; } - else if(curl_strequal(value, "qop")) { + else if(curlx_str_casecompare(&value, "qop")) { const char *token = content; struct Curl_str out; bool foundAuth = FALSE; bool foundAuthInt = FALSE; /* Pass leading spaces */ - while(*token && ISBLANK(*token)) - token++; + curlx_str_passblanks(&token); + while(!curlx_str_until(&token, &out, 32, ',')) { if(curlx_str_casecompare(&out, DIGEST_QOP_VALUE_STRING_AUTH)) foundAuth = TRUE; @@ -587,8 +583,7 @@ CURLcode Curl_auth_decode_digest_http_message(const char *chlg, foundAuthInt = TRUE; if(curlx_str_single(&token, ',')) break; - while(*token && ISBLANK(*token)) - token++; + curlx_str_passblanks(&token); } /* Select only auth or auth-int. Otherwise, ignore */ @@ -605,7 +600,7 @@ CURLcode Curl_auth_decode_digest_http_message(const char *chlg, return CURLE_OUT_OF_MEMORY; } } - else if(curl_strequal(value, "algorithm")) { + else if(curlx_str_casecompare(&value, "algorithm")) { curlx_free(digest->algorithm); digest->algorithm = curlx_strdup(content); if(!digest->algorithm) @@ -636,7 +631,7 @@ CURLcode Curl_auth_decode_digest_http_message(const char *chlg, else return CURLE_BAD_CONTENT_ENCODING; } - else if(curl_strequal(value, "userhash")) { + else if(curlx_str_casecompare(&value, "userhash")) { if(curl_strequal(content, "true")) { digest->userhash = TRUE; } @@ -649,12 +644,10 @@ CURLcode Curl_auth_decode_digest_http_message(const char *chlg, break; /* We are done here */ /* Pass all additional spaces here */ - while(*chlg && ISBLANK(*chlg)) - chlg++; + curlx_str_passblanks(&chlg); /* Allow the list to be comma-separated */ - if(',' == *chlg) - chlg++; + (void)curlx_str_single(&chlg, ','); } /* We had a nonce since before, and we got another one now without diff --git a/lib/vauth/digest.h b/lib/vauth/digest.h index 5b8f3ae7675f..3441f7626e9c 100644 --- a/lib/vauth/digest.h +++ b/lib/vauth/digest.h @@ -31,8 +31,8 @@ #define DIGEST_MAX_CONTENT_LENGTH 1024 /* This is used to extract the realm from a challenge message */ -bool Curl_auth_digest_get_pair(const char *str, char *value, char *content, - const char **endptr); +bool Curl_auth_digest_get_pair(const char *str, struct Curl_str *value, + char *content, const char **endptr); #endif diff --git a/lib/vauth/digest_sspi.c b/lib/vauth/digest_sspi.c index e3648e37b857..1a8dc91c627b 100644 --- a/lib/vauth/digest_sspi.c +++ b/lib/vauth/digest_sspi.c @@ -34,6 +34,7 @@ #include "curlx/multibyte.h" #include "curl_trc.h" #include "curlx/strdup.h" +#include "curlx/strparse.h" #include "strcase.h" #include "strerror.h" @@ -250,16 +251,15 @@ CURLcode Curl_override_sspi_http_realm(const char *chlg, /* If domain is blank or unset, check challenge message for realm */ if(!identity->Domain || !identity->DomainLength) { for(;;) { - char value[DIGEST_MAX_VALUE_LENGTH]; + struct Curl_str value; char content[DIGEST_MAX_CONTENT_LENGTH]; /* Pass all additional spaces here */ - while(*chlg && ISBLANK(*chlg)) - chlg++; + curlx_str_passblanks(&chlg); /* Extract a value=content pair */ - if(Curl_auth_digest_get_pair(chlg, value, content, &chlg)) { - if(curl_strequal(value, "realm")) { + if(Curl_auth_digest_get_pair(chlg, &value, content, &chlg)) { + if(curlx_str_casecompare(&value, "realm")) { /* Setup identity's domain and length */ domain.tchar_ptr = curlx_convert_UTF8_to_tchar(content); @@ -287,12 +287,10 @@ CURLcode Curl_override_sspi_http_realm(const char *chlg, break; /* We are done here */ /* Pass all additional spaces here */ - while(*chlg && ISBLANK(*chlg)) - chlg++; + curlx_str_passblanks(&chlg); /* Allow the list to be comma-separated */ - if(',' == *chlg) - chlg++; + (void)curlx_str_single(&chlg, ','); } } @@ -325,16 +323,16 @@ CURLcode Curl_auth_decode_digest_http_message(const char *chlg, /* Check for the 'stale' directive */ for(;;) { - char value[DIGEST_MAX_VALUE_LENGTH]; + struct Curl_str value; char content[DIGEST_MAX_CONTENT_LENGTH]; while(*p && ISBLANK(*p)) p++; - if(!Curl_auth_digest_get_pair(p, value, content, &p)) + if(!Curl_auth_digest_get_pair(p, &value, content, &p)) break; - if(curl_strequal(value, "stale") && + if(curlx_str_casecompare(&value, "stale") && curl_strequal(content, "true")) { stale = TRUE; break; From c5f9b8b40527ead840d9bbe19220e0b8515951eb Mon Sep 17 00:00:00 2001 From: Stefan Eissing Date: Fri, 4 Sep 2026 10:00:17 +0200 Subject: [PATCH 042/481] ftp: tighten use_ssl check for conn reuse CURLUSESSL_* needs a bit more special check on connection reuse. Except CURLUSESSL_TRY, we expect the reused connection to have exactly the same setting. On TRY, we are happy with anything that is no NONE. Closes #22823 --- lib/ftp.c | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/lib/ftp.c b/lib/ftp.c index f3cb75f2e3cb..ccdaf691c2f8 100644 --- a/lib/ftp.c +++ b/lib/ftp.c @@ -4479,16 +4479,15 @@ bool Curl_ftp_conns_match(struct connectdata *needle, struct connectdata *conn) cftpc->alternative_to_user) || (nftpc->ccc != cftpc->ccc)) return FALSE; - /* A mismatch on `use_ssl` MUST have been found in connection matching - * before we come here. This is a check on MAYBE/MUST use of STARTTLS and - * it only works on FTP. But IMAP/SMTP etc have the same `use_ssl` and - * no extra match like FTP. We lack tests in this area, so let FTP fail - * loudly here to help other cases. */ - if(nftpc->use_ssl > cftpc->use_ssl) { - DEBUGASSERT(0); - return FALSE; + + switch(nftpc->use_ssl) { + case CURLUSESSL_TRY: + /* A transfer that only "tries" SSL, is compatible with a connection + * of similar or stricter SSL use setting. */ + return (cftpc->use_ssl != CURLUSESSL_NONE); + default: + return (nftpc->use_ssl == cftpc->use_ssl); } - return TRUE; } /* From 7c0c7ca57b9774bff5e17f8ecf00db0cf5d7b67b Mon Sep 17 00:00:00 2001 From: Max Dymond Date: Fri, 4 Sep 2026 12:14:34 +0100 Subject: [PATCH 043/481] hash: select key operations by type Store a key type in each Curl_hash instead of hash and comparison function pointers, resolving the type to shared callbacks internally. Use byte keys for general hashes and native curl_socket_t keys for the multi event socket hash. Closes #22808 --- docs/internals/HASH.md | 16 ++--- lib/conncache.c | 4 +- lib/easy.c | 3 +- lib/hash.c | 130 ++++++++++++++++++++++++++++++++--------- lib/hash.h | 33 ++++------- lib/multi.c | 3 +- lib/multi_ev.c | 19 +----- lib/url.c | 8 +-- lib/vdns/dnscache.c | 2 +- tests/unit/unit1602.c | 3 +- tests/unit/unit1603.c | 60 ++++++++++++++++++- 11 files changed, 188 insertions(+), 93 deletions(-) diff --git a/docs/internals/HASH.md b/docs/internals/HASH.md index 9534eb7d1ac9..03be407328bc 100644 --- a/docs/internals/HASH.md +++ b/docs/internals/HASH.md @@ -19,8 +19,7 @@ Create a hash table. Add items. Retrieve items. Remove items. Destroy table. ~~~c void Curl_hash_init(struct Curl_hash *h, size_t slots, - hash_function hfunc, - comp_function comparator, + Curl_hash_type type, Curl_hash_dtor dtor); ~~~ @@ -28,15 +27,12 @@ The call initializes a `struct Curl_hash`. - `slots` is the number of entries to create in the hash table. Larger is better (faster lookups) but also uses more memory. -- `hfunc` is a function pointer to a function that returns a `size_t` value as - a checksum for an entry in this hash table. Ideally, it returns a unique - value for every entry ever added to the hash table, but hash collisions are - handled. -- `comparator` is a function pointer to a function that compares two hash - table entries. It should return non-zero if the compared items are - identical. +- `type` selects how keys are hashed and compared. `CURL_HASH_TYPE_BYTES` + treats keys as exact-length byte sequences. `CURL_HASH_TYPE_SOCKET` treats + keys as native `curl_socket_t` values and requires `key_len` to equal + `sizeof(curl_socket_t)`. - `dtor` is a function pointer to a destructor called when an entry is removed - from the table + from the table. ## `Curl_hash_add` diff --git a/lib/conncache.c b/lib/conncache.c index 84a335ae9b92..d4ae52f9f3c1 100644 --- a/lib/conncache.c +++ b/lib/conncache.c @@ -114,8 +114,8 @@ void Curl_cpool_init(struct cpool *cpool, struct Curl_share *share, size_t size) { - Curl_hash_init(&cpool->dest2bundle, size, Curl_hash_str, - curlx_str_key_compare, cpool_bundle_free_entry); + Curl_hash_init(&cpool->dest2bundle, size, CURL_HASH_TYPE_BYTES, + cpool_bundle_free_entry); cpool->share = share; cpool->initialized = TRUE; diff --git a/lib/easy.c b/lib/easy.c index 7572e44a2a8d..49a461ac94fe 100644 --- a/lib/easy.c +++ b/lib/easy.c @@ -1021,8 +1021,7 @@ CURL *curl_easy_duphandle(CURL *curl) */ outcurl->set.buffer_size = data->set.buffer_size; - Curl_hash_init(&outcurl->meta_hash, 23, - Curl_hash_str, curlx_str_key_compare, + Curl_hash_init(&outcurl->meta_hash, 23, CURL_HASH_TYPE_BYTES, dupeasy_meta_freeentry); curlx_dyn_init(&outcurl->state.headerb, CURL_MAX_HTTP_HEADER); Curl_bufref_init(&outcurl->state.url); diff --git a/lib/hash.c b/lib/hash.c index 3eae1d26a791..1c6cb0f60fa5 100644 --- a/lib/hash.c +++ b/lib/hash.c @@ -23,6 +23,8 @@ ***************************************************************************/ #include "curl_setup.h" +#include /* for offsetof() */ + #include "hash.h" /* random patterns for API verification */ @@ -31,6 +33,80 @@ #define ITERINIT 0x5FEDCBA9 #endif +typedef size_t (*hash_function)(void *key, + size_t key_length, + size_t slots_num); + +typedef size_t (*comp_function)(void *key1, + size_t key1_len, + void *key2, + size_t key2_len); + +/* Curl_hash stores its type in a uint8_t. */ +typedef char hash_types_fit_uint8[ + ((CURL_HASH_TYPE_LAST - 1) <= UINT8_MAX) ? 1 : -1]; + +struct hash_functions { + hash_function hash; + comp_function compare; +}; + +/* Avoid treating the variable-length key[1] as a one-byte object. */ +static char *hash_elem_key(struct Curl_hash_element *he) +{ + return (char *)he + offsetof(struct Curl_hash_element, key); +} + +static size_t hash_socket(void *key, size_t key_length, size_t slots_num) +{ + curl_socket_t socket; + + DEBUGASSERT(key_length == sizeof(socket)); + if(key_length != sizeof(socket)) + return 0; + memcpy(&socket, key, sizeof(socket)); + return (size_t)(socket % (curl_socket_t)slots_num); +} + +static size_t compare_socket(void *key1, size_t key1_len, + void *key2, size_t key2_len) +{ + curl_socket_t socket1; + curl_socket_t socket2; + + if((key1_len != sizeof(socket1)) || (key2_len != sizeof(socket2))) + return 0; + memcpy(&socket1, key1, sizeof(socket1)); + memcpy(&socket2, key2, sizeof(socket2)); + return socket1 == socket2; +} + +static size_t compare_bytes(void *key1, size_t key1_len, + void *key2, size_t key2_len) +{ + return (key1_len == key2_len) && !memcmp(key1, key2, key1_len); +} + +static const struct hash_functions hash_functions[] = { + { Curl_hash_str, compare_bytes }, + { hash_socket, compare_socket } +}; + +static const struct hash_functions *hash_get_functions(uint8_t type) +{ + DEBUGASSERT(CURL_ARRAYSIZE(hash_functions) == CURL_HASH_TYPE_LAST); + if((unsigned int)type >= CURL_HASH_TYPE_LAST) + type = CURL_HASH_TYPE_BYTES; + return &hash_functions[type]; +} + +static bool hash_key_is_valid(uint8_t type, size_t key_len) +{ + return (type == CURL_HASH_TYPE_BYTES) || + ((type == CURL_HASH_TYPE_SOCKET) && + (key_len == sizeof(curl_socket_t))); +} + #if 0 /* useful function for debugging hashes and their contents */ void Curl_hash_print(struct Curl_hash *h, void (*func)(void *)) { @@ -59,7 +135,7 @@ void Curl_hash_print(struct Curl_hash *h, void (*func)(void *)) func(he->ptr); else curl_mfprintf(stderr, " [key=%.*s, he=%p, ptr=%p]", - (int)he->key_len, (char *)he->key, + (int)he->key_len, hash_elem_key(he), (void *)he, (void *)he->ptr); he = Curl_hash_next_element(&iter); @@ -69,29 +145,25 @@ void Curl_hash_print(struct Curl_hash *h, void (*func)(void *)) #endif /* Initializes a hash structure. - * Return 1 on error, 0 is fine. * * @unittest: 1602 * @unittest: 1603 */ void Curl_hash_init(struct Curl_hash *h, size_t slots, - hash_function hfunc, - comp_function comparator, + Curl_hash_type type, Curl_hash_dtor dtor) { DEBUGASSERT(h); DEBUGASSERT(slots); - DEBUGASSERT(hfunc); - DEBUGASSERT(comparator); + DEBUGASSERT((unsigned int)type < CURL_HASH_TYPE_LAST); DEBUGASSERT(dtor); h->table = NULL; - h->hash_func = hfunc; - h->comp_func = comparator; h->dtor = dtor; h->size = 0; h->slots = slots; + h->type = (uint8_t)type; #ifdef DEBUGBUILD h->init = HASHINIT; #endif @@ -109,7 +181,7 @@ static struct Curl_hash_element *hash_elem_create(const void *key, if(he) { he->next = NULL; /* copy the key */ - memcpy(he->key, key, key_len); + memcpy(hash_elem_key(he), key, key_len); he->key_len = key_len; he->ptr = CURL_UNCONST(p); he->dtor = dtor; @@ -124,7 +196,7 @@ static void hash_elem_clear_ptr(struct Curl_hash *h, DEBUGASSERT(he); if(he->ptr) { if(he->dtor) - he->dtor(he->key, he->key_len, he->ptr); + he->dtor(hash_elem_key(he), he->key_len, he->ptr); else h->dtor(he->ptr); he->ptr = NULL; @@ -155,26 +227,27 @@ static void hash_elem_link(struct Curl_hash *h, ++h->size; } -#define CURL_HASH_SLOT(x, y, z) x->table[(x)->hash_func(y, z, (x)->slots)] -#define CURL_HASH_SLOT_ADDR(x, y, z) &CURL_HASH_SLOT(x, y, z) - void *Curl_hash_add2(struct Curl_hash *h, void *key, size_t key_len, void *p, Curl_hash_elem_dtor dtor) { + const struct hash_functions *functions; struct Curl_hash_element *he, **slot; DEBUGASSERT(h); DEBUGASSERT(h->slots); DEBUGASSERT(h->init == HASHINIT); + if(!hash_key_is_valid(h->type, key_len)) + return NULL; if(!h->table) { h->table = curlx_calloc(h->slots, sizeof(struct Curl_hash_element *)); if(!h->table) return NULL; /* OOM */ } - slot = CURL_HASH_SLOT_ADDR(h, key, key_len); + functions = hash_get_functions(h->type); + slot = &h->table[functions->hash(key, key_len, h->slots)]; for(he = *slot; he; he = he->next) { - if(h->comp_func(he->key, he->key_len, key, key_len)) { + if(functions->compare(hash_elem_key(he), he->key_len, key, key_len)) { /* existing key entry, overwrite by clearing old pointer */ hash_elem_clear_ptr(h, he); he->ptr = p; @@ -211,16 +284,21 @@ void *Curl_hash_add(struct Curl_hash *h, void *key, size_t key_len, void *p) */ int Curl_hash_delete(struct Curl_hash *h, void *key, size_t key_len) { + const struct hash_functions *functions; + DEBUGASSERT(h); DEBUGASSERT(h->slots); DEBUGASSERT(h->init == HASHINIT); + if(!hash_key_is_valid(h->type, key_len)) + return 1; if(h->table) { struct Curl_hash_element *he, **he_anchor; - he_anchor = CURL_HASH_SLOT_ADDR(h, key, key_len); + functions = hash_get_functions(h->type); + he_anchor = &h->table[functions->hash(key, key_len, h->slots)]; while(*he_anchor) { he = *he_anchor; - if(h->comp_func(he->key, he->key_len, key, key_len)) { + if(functions->compare(hash_elem_key(he), he->key_len, key, key_len)) { hash_elem_unlink(h, he_anchor, he); hash_elem_destroy(h, he); return 0; @@ -237,14 +315,19 @@ int Curl_hash_delete(struct Curl_hash *h, void *key, size_t key_len) */ void *Curl_hash_pick(struct Curl_hash *h, void *key, size_t key_len) { + const struct hash_functions *functions; + DEBUGASSERT(h); DEBUGASSERT(h->init == HASHINIT); + if(!hash_key_is_valid(h->type, key_len)) + return NULL; if(h->table) { struct Curl_hash_element *he; DEBUGASSERT(h->slots); - he = CURL_HASH_SLOT(h, key, key_len); + functions = hash_get_functions(h->type); + he = h->table[functions->hash(key, key_len, h->slots)]; while(he) { - if(h->comp_func(he->key, he->key_len, key, key_len)) { + if(functions->compare(hash_elem_key(he), he->key_len, key, key_len)) { return he->ptr; } he = he->next; @@ -338,15 +421,6 @@ size_t Curl_hash_str(void *key, size_t key_length, size_t slots_num) return (h % slots_num); } -size_t curlx_str_key_compare(void *k1, size_t key1_len, - void *k2, size_t key2_len) -{ - if((key1_len == key2_len) && !memcmp(k1, k2, key1_len)) - return 1; - - return 0; -} - void Curl_hash_start_iterate(struct Curl_hash *hash, struct Curl_hash_iterator *iter) { diff --git a/lib/hash.h b/lib/hash.h index d4c73b778647..12719dd92ff6 100644 --- a/lib/hash.h +++ b/lib/hash.h @@ -25,23 +25,17 @@ ***************************************************************************/ #include "curl_setup.h" -/* Hash function prototype */ -typedef size_t (*hash_function)(void *key, - size_t key_length, - size_t slots_num); - -/* - * Comparator function prototype. Compares two keys. - */ -typedef size_t (*comp_function)(void *key1, - size_t key1_len, - void *key2, - size_t key2_len); - typedef void (*Curl_hash_dtor)(void *); typedef void (*Curl_hash_elem_dtor)(void *key, size_t key_len, void *p); +/* How keys in a hash are interpreted. */ +typedef enum { + CURL_HASH_TYPE_BYTES, + CURL_HASH_TYPE_SOCKET, + CURL_HASH_TYPE_LAST +} Curl_hash_type; + struct Curl_hash_element { struct Curl_hash_element *next; void *ptr; @@ -52,15 +46,11 @@ struct Curl_hash_element { struct Curl_hash { struct Curl_hash_element **table; - - /* Hash function to be used for this hash table */ - hash_function hash_func; - /* Comparator function to compare keys */ - comp_function comp_func; - /* General element construct, unless element itself carries one */ + /* General element destructor, unless element itself carries one */ Curl_hash_dtor dtor; size_t slots; size_t size; + uint8_t type; /* Curl_hash_type */ #ifdef DEBUGBUILD int init; #endif @@ -77,8 +67,7 @@ struct Curl_hash_iterator { void Curl_hash_init(struct Curl_hash *h, size_t slots, - hash_function hfunc, - comp_function comparator, + Curl_hash_type type, Curl_hash_dtor dtor); void *Curl_hash_add(struct Curl_hash *h, void *key, size_t key_len, void *p); @@ -93,8 +82,6 @@ void Curl_hash_clean(struct Curl_hash *h); void Curl_hash_clean_with_criterium(struct Curl_hash *h, void *user, int (*comp)(void *, void *)); size_t Curl_hash_str(void *key, size_t key_length, size_t slots_num); -size_t curlx_str_key_compare(void *k1, size_t key1_len, void *k2, - size_t key2_len); void Curl_hash_start_iterate(struct Curl_hash *hash, struct Curl_hash_iterator *iter); struct Curl_hash_element *Curl_hash_next_element( diff --git a/lib/multi.c b/lib/multi.c index 25b5c4447bfd..9307401d8181 100644 --- a/lib/multi.c +++ b/lib/multi.c @@ -259,8 +259,7 @@ struct Curl_multi *Curl_multi_handle(uint32_t xfer_table_size, Curl_uint32_bset_init(&multi->dirty); Curl_uint32_bset_init(&multi->pending); Curl_uint32_bset_init(&multi->msgsent); - Curl_hash_init(&multi->proto_hash, 23, - Curl_hash_str, curlx_str_key_compare, ph_freeentry); + Curl_hash_init(&multi->proto_hash, 23, CURL_HASH_TYPE_BYTES, ph_freeentry); multi->multiplexing = TRUE; multi->max_concurrent_streams = 100; diff --git a/lib/multi_ev.c b/lib/multi_ev.c index 3b7eeb0dc3a6..ad489bbd859a 100644 --- a/lib/multi_ev.c +++ b/lib/multi_ev.c @@ -56,21 +56,6 @@ struct mev_sh_entry { callback at least once */ }; -static size_t mev_sh_entry_hash(void *key, size_t key_length, size_t slots_num) -{ - curl_socket_t fd = *((curl_socket_t *)key); - (void)key_length; - return (fd % (curl_socket_t)slots_num); -} - -static size_t mev_sh_entry_compare(void *k1, size_t k1_len, - void *k2, size_t k2_len) -{ - (void)k1_len; - (void)k2_len; - return (*((curl_socket_t *)k1)) == (*((curl_socket_t *)k2)); -} - /* sockhash entry destructor callback */ static void mev_sh_entry_dtor(void *freethis) { @@ -637,8 +622,8 @@ void Curl_multi_ev_conn_done(struct Curl_multi *multi, void Curl_multi_ev_init(struct Curl_multi *multi, size_t hashsize) { - Curl_hash_init(&multi->ev.sh_entries, hashsize, mev_sh_entry_hash, - mev_sh_entry_compare, mev_sh_entry_dtor); + Curl_hash_init(&multi->ev.sh_entries, hashsize, CURL_HASH_TYPE_SOCKET, + mev_sh_entry_dtor); } void Curl_multi_ev_cleanup(struct Curl_multi *multi) diff --git a/lib/url.c b/lib/url.c index 14557a46fe2d..29fc79464a00 100644 --- a/lib/url.c +++ b/lib/url.c @@ -470,8 +470,8 @@ CURLcode Curl_open(struct Curl_easy **curl) data->master_mid = UINT32_MAX; data->progress.hide = TRUE; - Curl_hash_init(&data->meta_hash, 23, - Curl_hash_str, curlx_str_key_compare, easy_meta_freeentry); + Curl_hash_init(&data->meta_hash, 23, CURL_HASH_TYPE_BYTES, + easy_meta_freeentry); DEBUGASSERT(STRING_LAST <= UINT8_MAX); Curl_u8_strset_init(&data->set.strings); curlx_dyn_init(&data->state.headerb, CURL_MAX_HTTP_HEADER); @@ -2016,8 +2016,8 @@ static CURLcode url_create_needle(struct Curl_easy *data, } /* Do the unfailable inits first, before checks that may early return */ - Curl_hash_init(&needle->meta_hash, 23, - Curl_hash_str, curlx_str_key_compare, conn_meta_freeentry); + Curl_hash_init(&needle->meta_hash, 23, CURL_HASH_TYPE_BYTES, + conn_meta_freeentry); /************************************************************* * Determine `conn->origin` and populate `data->state.up` and diff --git a/lib/vdns/dnscache.c b/lib/vdns/dnscache.c index bfd935ed472e..8993fabda085 100644 --- a/lib/vdns/dnscache.c +++ b/lib/vdns/dnscache.c @@ -807,7 +807,7 @@ static void dnscache_entry_dtor(void *entry) */ void Curl_dnscache_init(struct Curl_dnscache *dns, size_t size) { - Curl_hash_init(&dns->entries, size, Curl_hash_str, curlx_str_key_compare, + Curl_hash_init(&dns->entries, size, CURL_HASH_TYPE_BYTES, dnscache_entry_dtor); } diff --git a/tests/unit/unit1602.c b/tests/unit/unit1602.c index cca3667a0dbe..cc1ccf9bbb7e 100644 --- a/tests/unit/unit1602.c +++ b/tests/unit/unit1602.c @@ -32,8 +32,7 @@ static void t1602_mydtor(void *p) static CURLcode t1602_setup(struct Curl_hash *hash) { - Curl_hash_init(hash, 7, Curl_hash_str, - curlx_str_key_compare, t1602_mydtor); + Curl_hash_init(hash, 7, CURL_HASH_TYPE_BYTES, t1602_mydtor); return CURLE_OK; } diff --git a/tests/unit/unit1603.c b/tests/unit/unit1603.c index 6ba89999c02a..8185fe097fec 100644 --- a/tests/unit/unit1603.c +++ b/tests/unit/unit1603.c @@ -44,8 +44,7 @@ static void my_elem_dtor(void *key, size_t key_len, void *p) static CURLcode t1603_setup(struct Curl_hash *hash_static) { - Curl_hash_init(hash_static, slots, Curl_hash_str, - curlx_str_key_compare, t1603_mydtor); + Curl_hash_init(hash_static, slots, CURL_HASH_TYPE_BYTES, t1603_mydtor); return CURLE_OK; } @@ -54,6 +53,61 @@ static void t1603_stop(struct Curl_hash *hash_static) Curl_hash_destroy(hash_static); } +static void t1603_test_socket_type(void) +{ + struct Curl_hash hash; + curl_socket_t key = 1; + curl_socket_t collision = (curl_socket_t)(slots + 1); + int value = 1; + int collision_value = 2; + int replacement_value = 3; +#if SIZEOF_CURL_SOCKET_T > 4 + curl_socket_t high_key = ((curl_socket_t)1 << 32) + 1; + int high_value = 4; +#endif + + Curl_hash_init(&hash, slots, CURL_HASH_TYPE_SOCKET, t1603_mydtor); + fail_unless(!Curl_hash_add(&hash, &key, sizeof(key) - 1, &value), + "invalid socket key length was accepted"); + fail_unless(!hash.table, "invalid socket key allocated a hash table"); + fail_unless(!Curl_hash_pick(&hash, &key, sizeof(key) - 1), + "invalid socket key lookup succeeded"); + fail_unless(Curl_hash_delete(&hash, &key, sizeof(key) - 1), + "invalid socket key deletion succeeded"); + fail_unless(Curl_hash_add(&hash, &key, sizeof(key), &value) == &value, + "socket key insertion failed"); + fail_unless(hash.table && hash.table[1], + "socket key used the wrong hash slot"); + fail_unless(Curl_hash_add(&hash, &key, sizeof(key), &replacement_value) == + &replacement_value, "socket key replacement failed"); + fail_unless(Curl_hash_count(&hash) == 1, + "socket key replacement changed the hash count"); + fail_unless(Curl_hash_pick(&hash, &key, sizeof(key)) == &replacement_value, + "socket key replacement lookup failed"); + fail_unless(Curl_hash_add(&hash, &collision, sizeof(collision), + &collision_value) == &collision_value, + "colliding socket key insertion failed"); + fail_unless(Curl_hash_count(&hash) == 2, "wrong socket hash count"); + fail_unless(Curl_hash_pick(&hash, &collision, sizeof(collision)) == + &collision_value, "colliding socket key lookup failed"); +#if SIZEOF_CURL_SOCKET_T > 4 + fail_unless(Curl_hash_add(&hash, &high_key, sizeof(high_key), &high_value) == + &high_value, "wide socket key insertion failed"); + fail_unless(hash.table && hash.table[2], + "wide socket key used the wrong hash slot"); + fail_unless(Curl_hash_count(&hash) == 3, "wrong wide socket hash count"); + fail_unless(Curl_hash_pick(&hash, &high_key, sizeof(high_key)) == + &high_value, "wide socket key lookup failed"); +#endif + fail_unless(!Curl_hash_delete(&hash, &key, sizeof(key)), + "socket key deletion failed"); + fail_unless(!Curl_hash_pick(&hash, &key, sizeof(key)), + "deleted socket key was found"); + fail_unless(Curl_hash_pick(&hash, &collision, sizeof(collision)) == + &collision_value, "collision was lost after deletion"); + Curl_hash_destroy(&hash); +} + static CURLcode test_unit1603(const char *arg) { struct Curl_hash hash_static; @@ -174,5 +228,7 @@ static CURLcode test_unit1603(const char *arg) /* Clean up */ Curl_hash_clean(&hash_static); + t1603_test_socket_type(); + UNITTEST_END(t1603_stop(&hash_static)) } From 00271cfcb95b6481a1f91ce0a0dad5d64d2e89a9 Mon Sep 17 00:00:00 2001 From: Stefan Eissing Date: Fri, 4 Sep 2026 10:47:32 +0200 Subject: [PATCH 044/481] schannel: auto_client_cert conn matching Move flag `auto_client_cert` into the connection SSL config struct so it is part of connection matching. Closes #22827 --- lib/vtls/schannel.c | 5 ++--- lib/vtls/vtls_config.c | 9 ++++++--- lib/vtls/vtls_config.h | 4 ++-- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/lib/vtls/schannel.c b/lib/vtls/schannel.c index 9c73e1b2e18e..a9e6e5e3acac 100644 --- a/lib/vtls/schannel.c +++ b/lib/vtls/schannel.c @@ -780,7 +780,7 @@ static CURLcode schannel_acquire_credential_handle(struct Curl_cfilter *cf, "names in server certificates.")); } - if(!ssl_config->auto_client_cert) { + if(!conn_config->auto_client_cert) { flags &= ~(DWORD)SCH_CRED_USE_DEFAULT_CREDS; flags |= SCH_CRED_NO_DEFAULT_CREDS; infof(data, "schannel: disabled automatic use of client certificate"); @@ -850,7 +850,6 @@ static CURLcode schannel_connect_step1(struct Curl_cfilter *cf, struct schannel_ssl_backend_data *backend = (struct schannel_ssl_backend_data *)connssl->backend; struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); - struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); SecBuffer outbuf; SecBufferDesc outbuf_desc; SecBuffer inbuf; @@ -978,7 +977,7 @@ static CURLcode schannel_connect_step1(struct Curl_cfilter *cf, ISC_REQ_SEQUENCE_DETECT | ISC_REQ_REPLAY_DETECT | ISC_REQ_CONFIDENTIALITY | ISC_REQ_ALLOCATE_MEMORY | ISC_REQ_STREAM | - (!ssl_config->auto_client_cert ? ISC_REQ_USE_SUPPLIED_CREDS : 0); + (!conn_config->auto_client_cert ? ISC_REQ_USE_SUPPLIED_CREDS : 0); /* allocate memory for the security context handle */ backend->ctxt = (struct Curl_schannel_ctxt *) diff --git a/lib/vtls/vtls_config.c b/lib/vtls/vtls_config.c index 080d513bb598..932106450d3b 100644 --- a/lib/vtls/vtls_config.c +++ b/lib/vtls/vtls_config.c @@ -145,6 +145,7 @@ static bool match_ssl_primary_config(struct Curl_easy *data, (c1->verifypeer == c2->verifypeer) && (c1->verifyhost == c2->verifyhost) && (c1->verifystatus == c2->verifystatus) && + (c1->auto_client_cert == c2->auto_client_cert) && blobcmp(c1->cert_blob, c2->cert_blob) && blobcmp(c1->ca_info_blob, c2->ca_info_blob) && blobcmp(c1->issuercert_blob, c2->issuercert_blob) && @@ -196,6 +197,7 @@ static bool clone_ssl_primary_config(struct ssl_primary_config *source, dest->native_ca_store = source->native_ca_store; dest->cache_session = source->cache_session; dest->ssl_options = source->ssl_options; + dest->auto_client_cert = source->auto_client_cert; CLONE_BLOB(cert_blob); CLONE_BLOB(ca_info_blob); @@ -228,14 +230,15 @@ static void ssl_easy_config_compl_options(struct Curl_peer *origin, * If not, we switch it on for supported backends if no custom * CA settings exist. */ sslc->primary.native_ca_store = !!(options & CURLSSLOPT_NATIVE_CA); + sslc->primary.auto_client_cert = + Curl_peer_equal(origin, initial_origin) && + !!(options & CURLSSLOPT_AUTO_CLIENT_CERT); + sslc->enable_beast = !!(options & CURLSSLOPT_ALLOW_BEAST); sslc->no_partialchain = !!(options & CURLSSLOPT_NO_PARTIALCHAIN); sslc->no_revoke = !!(options & CURLSSLOPT_NO_REVOKE); sslc->revoke_best_effort = !!(options & CURLSSLOPT_REVOKE_BEST_EFFORT); sslc->earlydata = !!(options & CURLSSLOPT_EARLYDATA); - - sslc->auto_client_cert = Curl_peer_equal(origin, initial_origin) && - !!(options & CURLSSLOPT_AUTO_CLIENT_CERT); } static char *ssl_easy_steal(struct Curl_easy *data, enum dupstring id) diff --git a/lib/vtls/vtls_config.h b/lib/vtls/vtls_config.h index 8296604259bb..4c061bc0fd6d 100644 --- a/lib/vtls/vtls_config.h +++ b/lib/vtls/vtls_config.h @@ -57,6 +57,8 @@ struct ssl_primary_config { BIT(native_ca_store); /* use the native CA store of operating system */ BIT(cache_session); /* cache session or not */ BIT(deep_copy); /* members are deep copies, eg. owned here */ + BIT(auto_client_cert); /* automatically locate and use a client + certificate for authentication (Schannel) */ }; struct ssl_config_data { @@ -71,8 +73,6 @@ struct ssl_config_data { BIT(no_partialchain); /* do not accept partial certificate chains */ BIT(revoke_best_effort); /* ignore SSL revocation offline/missing revocation list errors */ - BIT(auto_client_cert); /* automatically locate and use a client - certificate for authentication (Schannel) */ BIT(custom_cafile); /* application has set custom CA file */ BIT(custom_capath); /* application has set custom CA path */ BIT(custom_cablob); /* application has set custom CA blob */ From e39ff5113b7c0d24e914d47bc719ae1eda601877 Mon Sep 17 00:00:00 2001 From: Huang Yiheng <272397091+yiheng-kkk@users.noreply.github.com> Date: Fri, 28 Aug 2026 04:13:41 +0800 Subject: [PATCH 045/481] http: reject resumed uploads HTTP has no interoperable mechanism for resuming POST or PUT uploads. Reject non-zero resume offsets for all HTTP upload request types instead of sending partial or complete data with non-standard semantics. Keep zero offsets and explicit --range uploads working. Update the tool errors, documentation, and tests. Fixes #22703 Closes #22712 --- docs/FAQ.md | 4 +- docs/cmdline-opts/continue-at.md | 6 +- docs/libcurl/opts/CURLOPT_RESUME_FROM.md | 4 + .../libcurl/opts/CURLOPT_RESUME_FROM_LARGE.md | 4 + lib/http.c | 32 +----- src/config2setopts.c | 26 +++-- tests/data/Makefile.am | 4 + tests/data/test1001 | 14 +-- tests/data/test1002 | 17 +-- tests/data/test1041 | 33 ++---- tests/data/test158 | 5 +- tests/data/test2058 | 12 +- tests/data/test2059 | 12 +- tests/data/test2060 | 12 +- tests/data/test2270 | 46 ++++++++ tests/data/test2271 | 38 +++++++ tests/data/test2272 | 42 +++++++ tests/data/test33 | 26 ++--- tests/data/test426 | 5 +- tests/libtest/Makefile.inc | 1 + tests/libtest/lib2270.c | 104 ++++++++++++++++++ 21 files changed, 319 insertions(+), 128 deletions(-) create mode 100644 tests/data/test2270 create mode 100644 tests/data/test2271 create mode 100644 tests/data/test2272 create mode 100644 tests/libtest/lib2270.c diff --git a/docs/FAQ.md b/docs/FAQ.md index 6cbba7d81110..2cc5c68ad446 100644 --- a/docs/FAQ.md +++ b/docs/FAQ.md @@ -339,8 +339,8 @@ files. ## How do I tell curl to resume a transfer? -curl supports resumed transfers both ways on both FTP and HTTP. Try the `-C` -option. +curl supports resumed HTTP, FTP and SFTP downloads as well as resumed FTP and +SFTP uploads. Try the `-C` option. ## Why does my posting using -F not work? diff --git a/docs/cmdline-opts/continue-at.md b/docs/cmdline-opts/continue-at.md index 67a137609306..3a9771a1a3fc 100644 --- a/docs/cmdline-opts/continue-at.md +++ b/docs/cmdline-opts/continue-at.md @@ -25,10 +25,8 @@ the FTP server command SIZE is not used by curl. Use "-C -" to instruct curl to automatically find out where/how to resume the transfer. It then uses the given output/input files to figure that out. -When using this option for HTTP uploads using POST or PUT, functionality is -not guaranteed. The HTTP protocol has no standard interoperable resume upload -and curl uses a set of headers for this purpose that once proved working for -some servers and have been left for those who find that useful. +HTTP uploads using POST or PUT cannot be resumed. curl returns an error when +you combine this option with such an upload. This command line option is mutually exclusive with --range: you can only use one of them for a single transfer. diff --git a/docs/libcurl/opts/CURLOPT_RESUME_FROM.md b/docs/libcurl/opts/CURLOPT_RESUME_FROM.md index d688430ba67d..4f286863980a 100644 --- a/docs/libcurl/opts/CURLOPT_RESUME_FROM.md +++ b/docs/libcurl/opts/CURLOPT_RESUME_FROM.md @@ -37,6 +37,10 @@ When doing uploads with FTP, the resume position is where in the local/source file libcurl should try to resume the upload from and it then appends the source file to the remote target file. +HTTP uploads using POST or PUT cannot be resumed. curl_easy_perform(3) returns +CURLE_BAD_FUNCTION_ARGUMENT when this option is set to a non-zero value for +such an upload. + If you need to resume a transfer beyond the 2GB limit, use CURLOPT_RESUME_FROM_LARGE(3) instead. diff --git a/docs/libcurl/opts/CURLOPT_RESUME_FROM_LARGE.md b/docs/libcurl/opts/CURLOPT_RESUME_FROM_LARGE.md index da1d9aa53f46..2ffaec446a92 100644 --- a/docs/libcurl/opts/CURLOPT_RESUME_FROM_LARGE.md +++ b/docs/libcurl/opts/CURLOPT_RESUME_FROM_LARGE.md @@ -38,6 +38,10 @@ When doing uploads with FTP, the resume position is where in the local/source file libcurl should try to resume the upload from and it appends the source file to the remote target file. +HTTP uploads using POST or PUT cannot be resumed. curl_easy_perform(3) returns +CURLE_BAD_FUNCTION_ARGUMENT when this option is set to a non-zero value for +such an upload. + # DEFAULT 0, not used diff --git a/lib/http.c b/lib/http.c index ef0bd39ed5ad..0ff7570089bc 100644 --- a/lib/http.c +++ b/lib/http.c @@ -2378,35 +2378,11 @@ static CURLcode set_reader(struct Curl_easy *data, Curl_HttpReq httpreq) static CURLcode http_resume(struct Curl_easy *data, Curl_HttpReq httpreq) { - if((HTTPREQ_POST == httpreq || HTTPREQ_PUT == httpreq) && + if((HTTPREQ_POST == httpreq || HTTPREQ_POST_FORM == httpreq || + HTTPREQ_POST_MIME == httpreq || HTTPREQ_PUT == httpreq) && data->state.resume_from) { - /********************************************************************** - * Resuming upload in HTTP means that we PUT or POST and that we have - * got a resume_from value set. The resume value has already created - * a Range: header that will be passed along. We need to "fast forward" - * the file the given number of bytes and decrease the assume upload - * file size before we continue this venture in the dark lands of HTTP. - * Resuming mime/form posting at an offset > 0 has no sense and is ignored. - *********************************************************************/ - - if(data->state.resume_from < 0) { - /* - * This is meant to get the size of the present remote-file by itself. - * We do not support this now. Bail out! - */ - data->state.resume_from = 0; - } - - if(data->state.resume_from && !data->req.authneg) { - /* only act on the first request */ - CURLcode result; - result = Curl_creader_resume_from(data, data->state.resume_from); - if(result) { - failf(data, "Unable to resume from offset %" FMT_OFF_T, - data->state.resume_from); - return result; - } - } + failf(data, "HTTP upload cannot be resumed"); + return CURLE_BAD_FUNCTION_ARGUMENT; } return CURLE_OK; } diff --git a/src/config2setopts.c b/src/config2setopts.c index 2bb859ac6830..1c823f16ae60 100644 --- a/src/config2setopts.c +++ b/src/config2setopts.c @@ -873,16 +873,25 @@ static CURLcode proxy_setopts(struct OperationConfig *config, CURL *curl) return result; } +static CURLcode check_post_resume(struct OperationConfig *config) +{ + if(config->use_resume && + (config->resume_from || config->resume_from_current)) { + errorf("(%d) HTTP upload cannot be resumed", + CURLE_BAD_FUNCTION_ARGUMENT); + config->synthetic_error = TRUE; + return CURLE_BAD_FUNCTION_ARGUMENT; + } + return CURLE_OK; +} + static CURLcode setopt_post(struct OperationConfig *config, CURL *curl) { CURLcode result = CURLE_OK; switch(config->httpreq) { case TOOL_HTTPREQ_SIMPLEPOST: - if(config->resume_from) { - errorf("cannot mix --continue-at with --data"); - result = CURLE_FAILED_INIT; - } - else { + result = check_post_resume(config); + if(!result) { MY_SETOPT_STR(curl, CURLOPT_POSTFIELDS, curlx_dyn_ptr(&config->postdata)); my_setopt_offt(curl, CURLOPT_POSTFIELDSIZE_LARGE, @@ -893,11 +902,8 @@ static CURLcode setopt_post(struct OperationConfig *config, CURL *curl) /* free previous remainders */ curl_mime_free(config->mimepost); config->mimepost = NULL; - if(config->resume_from) { - errorf("cannot mix --continue-at with --form"); - result = CURLE_FAILED_INIT; - } - else { + result = check_post_resume(config); + if(!result) { result = tool2curlmime(curl, config->mimeroot, &config->mimepost); if(!result) result = my_setopt_mimepost(curl, CURLOPT_MIMEPOST, config->mimepost); diff --git a/tests/data/Makefile.am b/tests/data/Makefile.am index f9aa384f8ebd..afdba994e9af 100644 --- a/tests/data/Makefile.am +++ b/tests/data/Makefile.am @@ -1930,6 +1930,10 @@ TESTCASES = \ test2207 \ test2208 \ \ + test2270 \ + test2271 \ + test2272 \ + \ test2300 \ test2301 \ test2302 \ diff --git a/tests/data/test1001 b/tests/data/test1001 index c1f839670f21..a17725a32cb3 100644 --- a/tests/data/test1001 +++ b/tests/data/test1001 @@ -6,7 +6,7 @@ HTTP HTTP PUT HTTP proxy HTTP Digest auth -Resume +Range Content-Range CUSTOMREQUEST @@ -70,10 +70,10 @@ proxy digest -HTTP POST --digest with PUT and resumed upload and modified method +HTTP Digest range upload with a modified method -http://%HOSTIP:%HTTPPORT/%TESTNUMBER -u auser:apasswd --digest -T %LOGDIR/%TESTNUMBER -x http://%HOSTIP:%HTTPPORT -C 2 -X GET +http://%HOSTIP:%HTTPPORT/%TESTNUMBER -u auser:apasswd --digest -T %LOGDIR/%TESTNUMBER -x http://%HOSTIP:%HTTPPORT --range 0-4 -X GET test @@ -85,7 +85,7 @@ test GET http://%HOSTIP:%HTTPPORT/%TESTNUMBER HTTP/1.1 Host: %HOSTIP:%HTTPPORT -Content-Range: bytes 2-4/5 +Content-Range: bytes 0-4/0 User-Agent: curl/%VERSION Accept: */* Proxy-Connection: Keep-Alive @@ -94,13 +94,13 @@ Content-Length: 0 GET http://%HOSTIP:%HTTPPORT/%TESTNUMBER HTTP/1.1 Host: %HOSTIP:%HTTPPORT Authorization: Digest username="auser", realm="testrealm", nonce="1053604144", uri="/%TESTNUMBER", response="6af4d89c952f4dd4cc215a6878dc499d" -Content-Range: bytes 2-4/5 +Content-Range: bytes 0-4/5 User-Agent: curl/%VERSION Accept: */* Proxy-Connection: Keep-Alive -Content-Length: 3 +Content-Length: 5 -st +test diff --git a/tests/data/test1002 b/tests/data/test1002 index eb53aac58cd6..5734cff949ac 100644 --- a/tests/data/test1002 +++ b/tests/data/test1002 @@ -7,7 +7,6 @@ HTTP PUT HTTP proxy HTTP Digest auth Resume -Content-Range CUSTOMREQUEST @@ -69,10 +68,10 @@ proxy digest -HTTP PUT with Digest auth, resumed upload and modified method, twice +HTTP Digest uploads with zero resume offset and custom method -http://%HOSTIP:%HTTPPORT/%TESTNUMBER.upload1 -T %LOGDIR/%TESTNUMBER http://%HOSTIP:%HTTPPORT/%TESTNUMBER.upload2 -T %LOGDIR/%TESTNUMBER -u auser:apasswd --digest -x http://%HOSTIP:%HTTPPORT -C 2 -X GET +http://%HOSTIP:%HTTPPORT/%TESTNUMBER.upload1 -T %LOGDIR/%TESTNUMBER http://%HOSTIP:%HTTPPORT/%TESTNUMBER.upload2 -T %LOGDIR/%TESTNUMBER -u auser:apasswd --digest -x http://%HOSTIP:%HTTPPORT -C 0 -X GET test @@ -84,7 +83,6 @@ test GET http://%HOSTIP:%HTTPPORT/%TESTNUMBER.upload1 HTTP/1.1 Host: %HOSTIP:%HTTPPORT -Content-Range: bytes 2-4/5 User-Agent: curl/%VERSION Accept: */* Proxy-Connection: Keep-Alive @@ -93,16 +91,14 @@ Content-Length: 0 GET http://%HOSTIP:%HTTPPORT/%TESTNUMBER.upload1 HTTP/1.1 Host: %HOSTIP:%HTTPPORT Authorization: Digest username="auser", realm="testrealm", nonce="1053604144", uri="/%TESTNUMBER.upload1", response="198aa9b6acb4b0c71d02a197a5e41f54" -Content-Range: bytes 2-4/5 User-Agent: curl/%VERSION Accept: */* Proxy-Connection: Keep-Alive -Content-Length: 3 +Content-Length: 5 -st +test GET http://%HOSTIP:%HTTPPORT/%TESTNUMBER.upload2 HTTP/1.1 Host: %HOSTIP:%HTTPPORT -Content-Range: bytes 2-4/5 User-Agent: curl/%VERSION Accept: */* Proxy-Connection: Keep-Alive @@ -111,13 +107,12 @@ Content-Length: 0 GET http://%HOSTIP:%HTTPPORT/%TESTNUMBER.upload2 HTTP/1.1 Host: %HOSTIP:%HTTPPORT Authorization: Digest username="auser", realm="testrealm", nonce="1053604144", uri="/%TESTNUMBER.upload2", response="d711f0d2042786d930de635ba0d1a1d0" -Content-Range: bytes 2-4/5 User-Agent: curl/%VERSION Accept: */* Proxy-Connection: Keep-Alive -Content-Length: 3 +Content-Length: 5 -st +test diff --git a/tests/data/test1041 b/tests/data/test1041 index 4133ac7a809d..961bf04ea411 100644 --- a/tests/data/test1041 +++ b/tests/data/test1041 @@ -6,13 +6,12 @@ HTTP HTTP PUT Resume -Content-Range # Server-side - + HTTP/1.1 200 OK swsclose Date: Tue, 09 Nov 2010 14:49:00 GMT Server: test-server/fake @@ -30,7 +29,7 @@ Content-Type: text/html http -HTTP PUT with resume from end of already-uploaded file +HTTP PUT rejects automatic upload resume 012345678 @@ -45,32 +44,20 @@ HTTP PUT with resume from end of already-uploaded file 012345678 -http://%HOSTIP:%HTTPPORT/%TESTNUMBER -T%LOGDIR/test%TESTNUMBER.txt -C - +http://%HOSTIP:%HTTPPORT/%TESTNUMBER -T%LOGDIR/test%TESTNUMBER.txt -C - --no-progress-meter # Verify data after the test has been "shot" -# curl does not do a HEAD request on the remote file so it has no idea whether -# it can skip part of the file or not. Instead, it sends the entire file. - -PUT /%TESTNUMBER HTTP/1.1 -Host: %HOSTIP:%HTTPPORT -Content-Range: bytes 0-99/100 -User-Agent: curl/%VERSION -Accept: */* -Content-Length: 100 + +43 + + +curl: (43) HTTP upload cannot be resumed + + -012345678 -012345678 -012345678 -012345678 -012345678 -012345678 -012345678 -012345678 -012345678 -012345678 diff --git a/tests/data/test158 b/tests/data/test158 index a6b1b94ea258..56afb9cd3d98 100644 --- a/tests/data/test158 +++ b/tests/data/test158 @@ -4,6 +4,7 @@ HTTP HTTP POST +Resume @@ -25,10 +26,10 @@ Mime http -HTTP multipart formpost with only a 100 reply +HTTP multipart formpost with zero resume offset and only a 100 reply -http://%HOSTIP:%HTTPPORT/%TESTNUMBER -F name=daniel +http://%HOSTIP:%HTTPPORT/%TESTNUMBER -F name=daniel -C 0 diff --git a/tests/data/test2058 b/tests/data/test2058 index 29c5c6456b5f..d4be197e1a3d 100644 --- a/tests/data/test2058 +++ b/tests/data/test2058 @@ -6,8 +6,6 @@ HTTP HTTP PUT HTTP proxy HTTP Digest auth -Resume -Content-Range CUSTOMREQUEST @@ -70,10 +68,10 @@ proxy digest -HTTP Digest with PUT, resumed upload, modified method and SHA-256 +HTTP Digest upload with a modified method and SHA-256 -http://%HOSTIP:%HTTPPORT/%TESTNUMBER -u auser:apasswd --digest -T %LOGDIR/%TESTNUMBER -x http://%HOSTIP:%HTTPPORT -C 2 -X GET +http://%HOSTIP:%HTTPPORT/%TESTNUMBER -u auser:apasswd --digest -T %LOGDIR/%TESTNUMBER -x http://%HOSTIP:%HTTPPORT -X GET test @@ -85,7 +83,6 @@ test GET http://%HOSTIP:%HTTPPORT/%TESTNUMBER HTTP/1.1 Host: %HOSTIP:%HTTPPORT -Content-Range: bytes 2-4/5 User-Agent: curl/%VERSION Accept: */* Proxy-Connection: Keep-Alive @@ -94,13 +91,12 @@ Content-Length: 0 GET http://%HOSTIP:%HTTPPORT/%TESTNUMBER HTTP/1.1 Host: %HOSTIP:%HTTPPORT Authorization: Digest username="auser", realm="testrealm", nonce="1053604144", uri="/%TESTNUMBER", response="fbed69f9f3fd304c8f1acb1a43eb32688b933c0e28055c16b926cbcec070aeed", algorithm=SHA-256 -Content-Range: bytes 2-4/5 User-Agent: curl/%VERSION Accept: */* Proxy-Connection: Keep-Alive -Content-Length: 3 +Content-Length: 5 -st +test diff --git a/tests/data/test2059 b/tests/data/test2059 index 71dc55f05963..f7a1af4390e0 100644 --- a/tests/data/test2059 +++ b/tests/data/test2059 @@ -6,8 +6,6 @@ HTTP HTTP PUT HTTP proxy HTTP Digest auth -Resume -Content-Range CUSTOMREQUEST @@ -70,10 +68,10 @@ proxy digest -HTTP Digest PUT, resumed upload, modified method, SHA-256 and userhash +HTTP Digest upload with a modified method, SHA-256 and userhash -http://%HOSTIP:%HTTPPORT/%TESTNUMBER -u auser:apasswd --digest -T %LOGDIR/%TESTNUMBER -x http://%HOSTIP:%HTTPPORT -C 2 -X GET +http://%HOSTIP:%HTTPPORT/%TESTNUMBER -u auser:apasswd --digest -T %LOGDIR/%TESTNUMBER -x http://%HOSTIP:%HTTPPORT -X GET test @@ -85,7 +83,6 @@ test GET http://%HOSTIP:%HTTPPORT/%TESTNUMBER HTTP/1.1 Host: %HOSTIP:%HTTPPORT -Content-Range: bytes 2-4/5 User-Agent: curl/%VERSION Accept: */* Proxy-Connection: Keep-Alive @@ -94,13 +91,12 @@ Content-Length: 0 GET http://%HOSTIP:%HTTPPORT/%TESTNUMBER HTTP/1.1 Host: %HOSTIP:%HTTPPORT Authorization: Digest username="fddc3bc7b753b73ab0848fd83cb20cbbca971258eb8d20c941dd5e0b010d66be", realm="testrealm", nonce="1053604144", uri="/%TESTNUMBER", response="22d200df1fd02a9d3a7269ef5bbb5bf8f16f184a74907df9b64a3755489c0b42", algorithm=SHA-256, userhash=true -Content-Range: bytes 2-4/5 User-Agent: curl/%VERSION Accept: */* Proxy-Connection: Keep-Alive -Content-Length: 3 +Content-Length: 5 -st +test diff --git a/tests/data/test2060 b/tests/data/test2060 index a623cd354b36..0a49dc1eca7f 100644 --- a/tests/data/test2060 +++ b/tests/data/test2060 @@ -6,8 +6,6 @@ HTTP HTTP PUT HTTP proxy HTTP Digest auth -Resume -Content-Range CUSTOMREQUEST @@ -70,10 +68,10 @@ proxy sha512-256 -HTTP Digest with PUT, resumed upload, SHA-512-256, userhash +HTTP Digest upload with a modified method and SHA-512-256 -http://%HOSTIP:%HTTPPORT/%TESTNUMBER -u auser:apasswd --digest -T %LOGDIR/%TESTNUMBER -x http://%HOSTIP:%HTTPPORT -C 2 -X GET +http://%HOSTIP:%HTTPPORT/%TESTNUMBER -u auser:apasswd --digest -T %LOGDIR/%TESTNUMBER -x http://%HOSTIP:%HTTPPORT -X GET test @@ -85,7 +83,6 @@ test GET http://%HOSTIP:%HTTPPORT/%TESTNUMBER HTTP/1.1 Host: %HOSTIP:%HTTPPORT -Content-Range: bytes 2-4/5 User-Agent: curl/%VERSION Accept: */* Proxy-Connection: Keep-Alive @@ -94,13 +91,12 @@ Content-Length: 0 GET http://%HOSTIP:%HTTPPORT/%TESTNUMBER HTTP/1.1 Host: %HOSTIP:%HTTPPORT Authorization: Digest username="auser", realm="testrealm", nonce="1053604144", uri="/%TESTNUMBER", response="691867f4a06c79fd0a175c1857e3df7015f6fff3ce8676497d2f1f805b5a8eca", algorithm=SHA-512-256 -Content-Range: bytes 2-4/5 User-Agent: curl/%VERSION Accept: */* Proxy-Connection: Keep-Alive -Content-Length: 3 +Content-Length: 5 -st +test diff --git a/tests/data/test2270 b/tests/data/test2270 new file mode 100644 index 000000000000..7e6f7f5ad431 --- /dev/null +++ b/tests/data/test2270 @@ -0,0 +1,46 @@ + + + + +HTTP +HTTP POST +HTTP MIME +Resume + + + +# Server-side + + +HTTP/1.1 200 OK swsclose + + + + +# Client-side + + +Mime +form-api + + +http + + +lib%TESTNUMBER + + +reject HTTP form and MIME upload resume + + +http://%HOSTIP:%HTTPPORT/%TESTNUMBER + + + +# Verify data after the test has been "shot" + + + + + + diff --git a/tests/data/test2271 b/tests/data/test2271 new file mode 100644 index 000000000000..f24b6c532a0c --- /dev/null +++ b/tests/data/test2271 @@ -0,0 +1,38 @@ + + + + +HTTP +HTTP POST +Resume +error detection + + + +# Server-side + + + +# Client-side + + +http + + +reject automatic HTTP data upload resume + + +http://%HOSTIP:%HTTPPORT/%TESTNUMBER -d foobar -C - + + + +# Verify data after the test has been "shot" + + +43 + + +curl: (43) HTTP upload cannot be resumed + + + diff --git a/tests/data/test2272 b/tests/data/test2272 new file mode 100644 index 000000000000..043c4676cc48 --- /dev/null +++ b/tests/data/test2272 @@ -0,0 +1,42 @@ + + + + +HTTP +HTTP POST +HTTP MIME +Resume +error detection + + + +# Server-side + + + +# Client-side + + +Mime + + +http + + +reject automatic HTTP form upload resume + + +http://%HOSTIP:%HTTPPORT/%TESTNUMBER -F name=value -C - + + + +# Verify data after the test has been "shot" + + +43 + + +curl: (43) HTTP upload cannot be resumed + + + diff --git a/tests/data/test33 b/tests/data/test33 index 2dab07cf8adc..82f8ef82c505 100644 --- a/tests/data/test33 +++ b/tests/data/test33 @@ -5,12 +5,11 @@ HTTP HTTP PUT Resume -Content-Range # Server-side - + HTTP/1.1 200 OK swsclose @@ -22,7 +21,7 @@ HTTP/1.1 200 OK swsclose http -HTTP PUT with resume +HTTP PUT rejects upload resume 012345678 @@ -37,25 +36,20 @@ HTTP PUT with resume 012345678 -http://%HOSTIP:%HTTPPORT/%TESTNUMBER -T%LOGDIR/test%TESTNUMBER.txt -C 50 +http://%HOSTIP:%HTTPPORT/%TESTNUMBER -T%LOGDIR/test%TESTNUMBER.txt -C 50 --no-progress-meter # Verify data after the test has been "shot" - -PUT /%TESTNUMBER HTTP/1.1 -Host: %HOSTIP:%HTTPPORT -Content-Range: bytes 50-99/100 -User-Agent: curl/%VERSION -Accept: */* -Content-Length: 50 + +43 + + +curl: (43) HTTP upload cannot be resumed + + -012345678 -012345678 -012345678 -012345678 -012345678 diff --git a/tests/data/test426 b/tests/data/test426 index 1f2443dbfe68..e6b80d90a2c5 100644 --- a/tests/data/test426 +++ b/tests/data/test426 @@ -27,7 +27,10 @@ http://%HOSTIP:%HTTPPORT/%TESTNUMBER -d foobar -C 3 # Verify data after the test has been "shot" -2 +43 + +curl: (43) HTTP upload cannot be resumed + diff --git a/tests/libtest/Makefile.inc b/tests/libtest/Makefile.inc index cb0e7ea9b04e..aaa9936cd490 100644 --- a/tests/libtest/Makefile.inc +++ b/tests/libtest/Makefile.inc @@ -294,6 +294,7 @@ TESTS_C = \ lib2032.c \ lib2082.c \ lib2118.c \ + lib2270.c \ lib2301.c \ lib2302.c \ lib2304.c \ diff --git a/tests/libtest/lib2270.c b/tests/libtest/lib2270.c new file mode 100644 index 000000000000..17ec606c91a7 --- /dev/null +++ b/tests/libtest/lib2270.c @@ -0,0 +1,104 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ +#include "first.h" + +static CURLcode test_lib2270(const char *URL) +{ + CURL *curl = NULL; + curl_mime *mime = NULL; + curl_mimepart *part; + struct curl_httppost *formpost = NULL; + struct curl_httppost *lastptr = NULL; + CURLFORMcode formrc; + CURLcode result = TEST_ERR_MAJOR_BAD; + + if(curl_global_init(CURL_GLOBAL_ALL) != CURLE_OK) { + curl_mfprintf(stderr, "curl_global_init() failed\n"); + return TEST_ERR_MAJOR_BAD; + } + + curl = curl_easy_init(); + if(!curl) { + curl_mfprintf(stderr, "curl_easy_init() failed\n"); + goto test_cleanup; + } + + mime = curl_mime_init(curl); + if(!mime) { + curl_mfprintf(stderr, "curl_mime_init() failed\n"); + goto test_cleanup; + } + part = curl_mime_addpart(mime); + if(!part) { + curl_mfprintf(stderr, "curl_mime_addpart() failed\n"); + goto test_cleanup; + } + result = curl_mime_name(part, "field"); + if(result) + goto test_cleanup; + result = curl_mime_data(part, "value", CURL_ZERO_TERMINATED); + if(result) + goto test_cleanup; + + easy_setopt(curl, CURLOPT_URL, URL); + easy_setopt(curl, CURLOPT_MIMEPOST, mime); + easy_setopt(curl, CURLOPT_RESUME_FROM_LARGE, (curl_off_t)1); + result = curl_easy_perform(curl); + if(result != CURLE_BAD_FUNCTION_ARGUMENT) { + curl_mfprintf(stderr, "MIME resume returned %d, expected %d\n", + (int)result, CURLE_BAD_FUNCTION_ARGUMENT); + result = TEST_ERR_FAILURE; + goto test_cleanup; + } + + easy_setopt(curl, CURLOPT_MIMEPOST, NULL); + formrc = curl_formadd(&formpost, &lastptr, + CURLFORM_COPYNAME, "field", + CURLFORM_COPYCONTENTS, "value", + CURLFORM_END); + if(formrc) { + curl_mfprintf(stderr, "curl_formadd() failed with code %d\n", + (int)formrc); + result = TEST_ERR_FAILURE; + goto test_cleanup; + } + + easy_setopt(curl, CURLOPT_HTTPPOST, formpost); + easy_setopt(curl, CURLOPT_RESUME_FROM_LARGE, (curl_off_t)-1); + result = curl_easy_perform(curl); + if(result != CURLE_BAD_FUNCTION_ARGUMENT) { + curl_mfprintf(stderr, "form resume returned %d, expected %d\n", + (int)result, CURLE_BAD_FUNCTION_ARGUMENT); + result = TEST_ERR_FAILURE; + goto test_cleanup; + } + result = CURLE_OK; + +test_cleanup: + curl_easy_cleanup(curl); + curl_mime_free(mime); + curl_formfree(formpost); + curl_global_cleanup(); + return result; +} From 8071d7adc2ce8b29a49b8888aed1c346eaea4a3f Mon Sep 17 00:00:00 2001 From: Martin Dukek Date: Fri, 28 Aug 2026 14:01:25 +0200 Subject: [PATCH 046/481] schannel: clear borrowed sslContext on close The current Negotiate and NTLM consumers were traced, and no reachable post-free dereference was found. The pointer is still left dangling after Schannel teardown, so clear it when the context is destroyed. Closes https://github.com/curl/curl/pull/22720 --- lib/vtls/schannel.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/vtls/schannel.c b/lib/vtls/schannel.c index a9e6e5e3acac..b4372a6f6223 100644 --- a/lib/vtls/schannel.c +++ b/lib/vtls/schannel.c @@ -2551,6 +2551,9 @@ static void schannel_close(struct Curl_cfilter *cf, struct Curl_easy *data) /* free SSPI Schannel API security context handle */ if(backend->ctxt) { + if(cf->conn->sslContext == &backend->ctxt->ctxt_handle) + cf->conn->sslContext = NULL; + DEBUGF(infof(data, "schannel: clear security context handle")); Curl_pSecFn->DeleteSecurityContext(&backend->ctxt->ctxt_handle); curlx_safefree(backend->ctxt); From 3300ce0fb53e0e394b94efa1869350de95877d56 Mon Sep 17 00:00:00 2001 From: Viktor Szakats Date: Sat, 5 Sep 2026 13:10:24 +0200 Subject: [PATCH 047/481] digest.h: add forward declaration Follow-up to 9be5c6849225f2dbb3bf60249998c33575dd0910 #22831 Closes #22834 --- lib/vauth/digest.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/vauth/digest.h b/lib/vauth/digest.h index 3441f7626e9c..63d8a6122c25 100644 --- a/lib/vauth/digest.h +++ b/lib/vauth/digest.h @@ -30,6 +30,8 @@ #define DIGEST_MAX_VALUE_LENGTH 256 #define DIGEST_MAX_CONTENT_LENGTH 1024 +struct Curl_str; + /* This is used to extract the realm from a challenge message */ bool Curl_auth_digest_get_pair(const char *str, struct Curl_str *value, char *content, const char **endptr); From 55ac6ef0df4ee90421aea141fd071758f1a64766 Mon Sep 17 00:00:00 2001 From: Daniel Stenberg Date: Sat, 5 Sep 2026 14:17:12 +0200 Subject: [PATCH 048/481] test 4657: -J with multiple redirects, use the last name Closes #22835 --- tests/data/Makefile.am | 1 + tests/data/test4657 | 89 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 90 insertions(+) create mode 100644 tests/data/test4657 diff --git a/tests/data/Makefile.am b/tests/data/Makefile.am index afdba994e9af..a7e40b9fb7b0 100644 --- a/tests/data/Makefile.am +++ b/tests/data/Makefile.am @@ -2101,6 +2101,7 @@ TESTCASES = \ test4000 \ test4001 \ test4644 \ + test4657 \ test5000 \ test5001 \ test5002 \ diff --git a/tests/data/test4657 b/tests/data/test4657 new file mode 100644 index 000000000000..4fbd66d43332 --- /dev/null +++ b/tests/data/test4657 @@ -0,0 +1,89 @@ + + + + +--remote-header-name + + + +# Server-side + + + +HTTP/1.1 301 OK +Location: %TESTNUMBER0002 +Content-Length: 0 + + + + +HTTP/1.1 301 OK +Location: %TESTNUMBER0003 +Content-Length: 0 + + + + +HTTP/1.1 301 OK +Location: %TESTNUMBER0004 +Content-Length: 0 + + + + +HTTP/1.1 200 OK +Content-Length: 8 + +content + + + + +# Client-side + + +IPv6 +proxy + + +http-proxy +http +https + + +-J with multiple redirects, use the last name + + +-L -O -J http://%HOSTIP:%HTTPPORT --output-dir=%LOGDIR + + + +# Verify data after the test has been "shot" + + +GET / HTTP/1.1 +Host: %HOSTIP:%HTTPPORT +User-Agent: curl/%VERSION +Accept: */* + +GET /%TESTNUMBER0002 HTTP/1.1 +Host: %HOSTIP:%HTTPPORT +User-Agent: curl/%VERSION +Accept: */* + +GET /%TESTNUMBER0003 HTTP/1.1 +Host: %HOSTIP:%HTTPPORT +User-Agent: curl/%VERSION +Accept: */* + +GET /%TESTNUMBER0004 HTTP/1.1 +Host: %HOSTIP:%HTTPPORT +User-Agent: curl/%VERSION +Accept: */* + + + +content + + + From 6ded3e594455d6f018fab5f2f5d916eb9bc4ef94 Mon Sep 17 00:00:00 2001 From: Daniel Stenberg Date: Sat, 5 Sep 2026 16:08:55 +0200 Subject: [PATCH 049/481] cookie: ignore individual cookie errors when loading Closes #22840 --- lib/cookie.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/cookie.c b/lib/cookie.c index 3255668ec7b9..5077b598497d 100644 --- a/lib/cookie.c +++ b/lib/cookie.c @@ -1198,8 +1198,9 @@ static CURLcode cookie_load(struct Curl_easy *data, const char *file, (headerline ? COOKIE_HTTPHEADER : 0) | COOKIE_NOEXPIRE | COOKIE_SECURE | (flags & COOKIE_NOPSL)); - /* File reading cookie failures are not propagated back to the - caller because there is no way to do that */ + /* Ignore individual cookie problems unless we ran out of memory */ + if(result != CURLE_OUT_OF_MEMORY) + result = CURLE_OK; } } while(!result && !eof); curlx_dyn_free(&buf); /* free the line buffer */ From 65f685742ccf40d685cb2899618125bd25a49a66 Mon Sep 17 00:00:00 2001 From: Daniel Stenberg Date: Sat, 5 Sep 2026 17:16:33 +0200 Subject: [PATCH 050/481] lib: rename curlx/fopen.[ch] to curlx/win32-fopen.[ch] To make it more apparent what the file is for and separate it better from curl_fopen.[ch] Closes #22842 --- .github/labeler.yml | 2 +- lib/Makefile.inc | 4 ++-- lib/curl_fopen.h | 2 +- lib/curlx/{fopen.c => win32-fopen.c} | 2 +- lib/curlx/{fopen.h => win32-fopen.h} | 0 lib/file.c | 2 +- lib/formdata.c | 2 +- lib/memdebug.c | 2 +- lib/mime.c | 2 +- lib/netrc.c | 2 +- lib/vquic/cf-ngtcp2-cmn.c | 2 +- lib/vquic/cf-ngtcp2.c | 2 +- lib/vquic/vquic.c | 2 +- lib/vssh/libssh2.c | 2 +- lib/vssh/vssh.c | 2 +- lib/vtls/gtls.c | 2 +- lib/vtls/keylog.c | 2 +- lib/vtls/rustls.c | 2 +- lib/vtls/schannel.c | 2 +- lib/vtls/schannel_verify.c | 2 +- lib/vtls/vtls.c | 2 +- src/Makefile.inc | 4 ++-- src/tool_setup.h | 2 +- tests/libtest/Makefile.inc | 2 +- tests/libtest/first.h | 2 +- tests/perf/Makefile.inc | 2 +- tests/perf/first.h | 2 +- tests/server/Makefile.inc | 2 +- tests/server/first.h | 2 +- 29 files changed, 30 insertions(+), 30 deletions(-) rename lib/curlx/{fopen.c => win32-fopen.c} (99%) rename lib/curlx/{fopen.h => win32-fopen.h} (100%) diff --git a/.github/labeler.yml b/.github/labeler.yml index 706c2d1d0648..0b7dedfea740 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -564,7 +564,7 @@ Windows: appveyor.*,\ CMake/win32-cache.cmake,\ lib/*win32*,\ - lib/curlx/fopen.*,\ + lib/curlx/win32-fopen.*,\ lib/curlx/multibyte.*,\ lib/curlx/winapi.*,\ lib/libcurl.def,\ diff --git a/lib/Makefile.inc b/lib/Makefile.inc index 42ebc8b274f3..330ae929b573 100644 --- a/lib/Makefile.inc +++ b/lib/Makefile.inc @@ -27,7 +27,7 @@ LIB_CURLX_CFILES = \ curlx/base64.c \ curlx/basename.c \ curlx/dynbuf.c \ - curlx/fopen.c \ + curlx/win32-fopen.c \ curlx/inet_ntop.c \ curlx/inet_pton.c \ curlx/multibyte.c \ @@ -48,7 +48,7 @@ LIB_CURLX_HFILES = \ curlx/base64.h \ curlx/basename.h \ curlx/dynbuf.h \ - curlx/fopen.h \ + curlx/win32-fopen.h \ curlx/inet_ntop.h \ curlx/inet_pton.h \ curlx/multibyte.h \ diff --git a/lib/curl_fopen.h b/lib/curl_fopen.h index bd402d44894a..5bdd2f20f8d9 100644 --- a/lib/curl_fopen.h +++ b/lib/curl_fopen.h @@ -23,7 +23,7 @@ * SPDX-License-Identifier: curl * ***************************************************************************/ -#include "curlx/fopen.h" +#include "curlx/win32-fopen.h" CURLcode Curl_fopen(struct Curl_easy *data, const char *filename, FILE **fh, char **tempname); diff --git a/lib/curlx/fopen.c b/lib/curlx/win32-fopen.c similarity index 99% rename from lib/curlx/fopen.c rename to lib/curlx/win32-fopen.c index 09264aeb101a..24738e9a9351 100644 --- a/lib/curlx/fopen.c +++ b/lib/curlx/win32-fopen.c @@ -23,7 +23,7 @@ ***************************************************************************/ #include "curl_setup.h" -#include "curlx/fopen.h" +#include "curlx/win32-fopen.h" int curlx_fseek(void *stream, curl_off_t offset, int whence) { diff --git a/lib/curlx/fopen.h b/lib/curlx/win32-fopen.h similarity index 100% rename from lib/curlx/fopen.h rename to lib/curlx/win32-fopen.h diff --git a/lib/file.c b/lib/file.c index d3553b7df2d1..e674a16923a1 100644 --- a/lib/file.c +++ b/lib/file.c @@ -59,7 +59,7 @@ #include "transfer.h" #include "url.h" #include "parsedate.h" /* for the week day and month names */ -#include "curlx/fopen.h" +#include "curlx/win32-fopen.h" #include "curl_range.h" #if defined(_WIN32) || defined(MSDOS) diff --git a/lib/formdata.c b/lib/formdata.c index 627744f19aa4..60b65a2c21ad 100644 --- a/lib/formdata.c +++ b/lib/formdata.c @@ -33,7 +33,7 @@ struct Curl_easy; #include "mime.h" #include "curlx/strdup.h" #include "bufref.h" -#include "curlx/fopen.h" +#include "curlx/win32-fopen.h" #define HTTPPOST_PTRNAME CURL_HTTPPOST_PTRNAME diff --git a/lib/memdebug.c b/lib/memdebug.c index b6af2c6d3528..53c85291e09c 100644 --- a/lib/memdebug.c +++ b/lib/memdebug.c @@ -29,7 +29,7 @@ #include "urldata.h" #include "curl_threads.h" -#include "curlx/fopen.h" /* for CURLX_FOPEN_LOW(), CURLX_FREOPEN_LOW() */ +#include "curlx/win32-fopen.h" /* for CURLX_FOPEN_*() */ #ifdef USE_BACKTRACE #include diff --git a/lib/mime.c b/lib/mime.c index 0e23259e6625..635eb310ad20 100644 --- a/lib/mime.c +++ b/lib/mime.c @@ -33,7 +33,7 @@ struct Curl_easy; #include "curlx/strdup.h" #include "curlx/basename.h" #include "curlx/strcopy.h" -#include "curlx/fopen.h" +#include "curlx/win32-fopen.h" #include "curlx/base64.h" #if !defined(CURL_DISABLE_MIME) && (!defined(CURL_DISABLE_HTTP) || \ diff --git a/lib/netrc.c b/lib/netrc.c index 7c8e1fb2ab3f..d78529833dcf 100644 --- a/lib/netrc.c +++ b/lib/netrc.c @@ -41,7 +41,7 @@ #include "curl_trc.h" #include "strcase.h" #include "curl_get_line.h" -#include "curlx/fopen.h" +#include "curlx/win32-fopen.h" #include "curlx/strparse.h" diff --git a/lib/vquic/cf-ngtcp2-cmn.c b/lib/vquic/cf-ngtcp2-cmn.c index b1cfb68cbdfa..f80e56e341f7 100644 --- a/lib/vquic/cf-ngtcp2-cmn.c +++ b/lib/vquic/cf-ngtcp2-cmn.c @@ -57,7 +57,7 @@ #include "cf-socket.h" #include "connect.h" #include "progress.h" -#include "curlx/fopen.h" +#include "curlx/win32-fopen.h" #include "curlx/dynbuf.h" #include "http1.h" #include "select.h" diff --git a/lib/vquic/cf-ngtcp2.c b/lib/vquic/cf-ngtcp2.c index 0ebf849789b2..83698a7f3e33 100644 --- a/lib/vquic/cf-ngtcp2.c +++ b/lib/vquic/cf-ngtcp2.c @@ -35,7 +35,7 @@ #include "cf-socket.h" #include "connect.h" #include "progress.h" -#include "curlx/fopen.h" +#include "curlx/win32-fopen.h" #include "curlx/dynbuf.h" #include "http1.h" #include "select.h" diff --git a/lib/vquic/vquic.c b/lib/vquic/vquic.c index 44103d3d6641..c6371368bdd4 100644 --- a/lib/vquic/vquic.c +++ b/lib/vquic/vquic.c @@ -50,7 +50,7 @@ #include "bufq.h" #include "curlx/dynbuf.h" -#include "curlx/fopen.h" +#include "curlx/win32-fopen.h" #include "cfilters.h" #include "vdns/cf-dns.h" #include "vquic/cf-ngtcp2.h" diff --git a/lib/vssh/libssh2.c b/lib/vssh/libssh2.c index 24a651d30ebb..a528e1e4e621 100644 --- a/lib/vssh/libssh2.c +++ b/lib/vssh/libssh2.c @@ -53,7 +53,7 @@ #include "parsedate.h" /* for the week day and month names */ #include "multiif.h" #include "select.h" -#include "curlx/fopen.h" +#include "curlx/win32-fopen.h" #include "vssh/vssh.h" #include "curlx/strparse.h" #include "curlx/base64.h" /* for curlx_base64_encode() */ diff --git a/lib/vssh/vssh.c b/lib/vssh/vssh.c index 9834546987d9..2a1bbb0e76b1 100644 --- a/lib/vssh/vssh.c +++ b/lib/vssh/vssh.c @@ -32,7 +32,7 @@ #include "escape.h" #include "select.h" /* for Curl_pollset_change() */ #include "url.h" /* for Curl_conn_meta_get() */ -#include "curlx/fopen.h" +#include "curlx/win32-fopen.h" #ifdef CURLVERBOSE const char *Curl_ssh_statename(sshstate state) diff --git a/lib/vtls/gtls.c b/lib/vtls/gtls.c index 2b85d4a7801e..ed7a010e5eb3 100644 --- a/lib/vtls/gtls.c +++ b/lib/vtls/gtls.c @@ -52,7 +52,7 @@ #include "connect.h" /* for the connect timeout */ #include "progress.h" #include "curlx/strdup.h" -#include "curlx/fopen.h" +#include "curlx/win32-fopen.h" #include "vtls/x509asn1.h" /* Enable GnuTLS debugging by defining GTLSDEBUG */ diff --git a/lib/vtls/keylog.c b/lib/vtls/keylog.c index ac9649a41f78..d5bf560c32ea 100644 --- a/lib/vtls/keylog.c +++ b/lib/vtls/keylog.c @@ -28,7 +28,7 @@ defined(USE_RUSTLS) #include "escape.h" -#include "curlx/fopen.h" +#include "curlx/win32-fopen.h" /* The fp for the open SSLKEYLOGFILE, or NULL if not open */ static FILE *keylog_file_fp; diff --git a/lib/vtls/rustls.c b/lib/vtls/rustls.c index c21989037581..330ea29f1fe7 100644 --- a/lib/vtls/rustls.c +++ b/lib/vtls/rustls.c @@ -29,7 +29,7 @@ #include -#include "curlx/fopen.h" +#include "curlx/win32-fopen.h" #include "curlx/strerr.h" #include "urldata.h" #include "curl_trc.h" diff --git a/lib/vtls/schannel.c b/lib/vtls/schannel.c index b4372a6f6223..1cf1f7d5fca6 100644 --- a/lib/vtls/schannel.c +++ b/lib/vtls/schannel.c @@ -45,7 +45,7 @@ #include "curlx/strdup.h" #include "strerror.h" #include "select.h" /* for the socket readiness */ -#include "curlx/fopen.h" +#include "curlx/win32-fopen.h" #include "curlx/multibyte.h" #include "vtls/x509asn1.h" #include "curlx/version_win32.h" diff --git a/lib/vtls/schannel_verify.c b/lib/vtls/schannel_verify.c index 6e55c9b839ba..bcac42ab5564 100644 --- a/lib/vtls/schannel_verify.c +++ b/lib/vtls/schannel_verify.c @@ -43,7 +43,7 @@ #include "vtls/vtls_int.h" #include "curl_trc.h" #include "strerror.h" -#include "curlx/fopen.h" +#include "curlx/win32-fopen.h" #include "curlx/inet_pton.h" #include "curlx/multibyte.h" #include "curlx/version_win32.h" diff --git a/lib/vtls/vtls.c b/lib/vtls/vtls.c index f6cc370890d9..8a90c7586af1 100644 --- a/lib/vtls/vtls.c +++ b/lib/vtls/vtls.c @@ -60,7 +60,7 @@ #include "strcase.h" #include "url.h" #include "progress.h" -#include "curlx/fopen.h" +#include "curlx/win32-fopen.h" #include "curl_sha256.h" #include "curlx/base64.h" #include "curlx/inet_pton.h" diff --git a/src/Makefile.inc b/src/Makefile.inc index 84c0a3a02de7..45ee6a30b8ea 100644 --- a/src/Makefile.inc +++ b/src/Makefile.inc @@ -36,7 +36,7 @@ CURLX_CFILES = \ ../lib/curlx/base64.c \ ../lib/curlx/basename.c \ ../lib/curlx/dynbuf.c \ - ../lib/curlx/fopen.c \ + ../lib/curlx/win32-fopen.c \ ../lib/curlx/multibyte.c \ ../lib/curlx/nonblock.c \ ../lib/curlx/strcopy.c \ @@ -55,7 +55,7 @@ CURLX_HFILES = \ ../lib/curlx/base64.h \ ../lib/curlx/basename.h \ ../lib/curlx/dynbuf.h \ - ../lib/curlx/fopen.h \ + ../lib/curlx/win32-fopen.h \ ../lib/curlx/multibyte.h \ ../lib/curlx/nonblock.h \ ../lib/curlx/strcopy.h \ diff --git a/src/tool_setup.h b/src/tool_setup.h index 7e77c114f1c4..7556e6cd4b3d 100644 --- a/src/tool_setup.h +++ b/src/tool_setup.h @@ -47,7 +47,7 @@ extern FILE *tool_stderr; #include "curlx/base64.h" /* for curlx_base64* */ #include "curlx/dynbuf.h" /* for curlx_dyn_*() */ -#include "curlx/fopen.h" /* for curlx_f*() */ +#include "curlx/win32-fopen.h" /* for curlx_f*() */ #include "curlx/multibyte.h" /* for curlx_convert_*() */ #include "curlx/strcopy.h" /* for curlx_strcopy() */ #include "curlx/strdup.h" /* for curlx_memdup*() */ diff --git a/tests/libtest/Makefile.inc b/tests/libtest/Makefile.inc index aaa9936cd490..3bd7b5f6962f 100644 --- a/tests/libtest/Makefile.inc +++ b/tests/libtest/Makefile.inc @@ -35,7 +35,7 @@ UTILS_H = testutil.h testtrace.h unitcheck.h CURLX_C = \ ../../lib/curl_threads.c \ - ../../lib/curlx/fopen.c \ + ../../lib/curlx/win32-fopen.c \ ../../lib/curlx/inet_ntop.c \ ../../lib/curlx/inet_pton.c \ ../../lib/curlx/multibyte.c \ diff --git a/tests/libtest/first.h b/tests/libtest/first.h index 75ae75c6fb43..d5a3ec45fde0 100644 --- a/tests/libtest/first.h +++ b/tests/libtest/first.h @@ -46,7 +46,7 @@ extern int unitfail; /* for unittests */ #include "curlx/base64.h" /* for curlx_base64* */ #include "curlx/dynbuf.h" /* for curlx_dyn_*() */ -#include "curlx/fopen.h" /* for curlx_f*() */ +#include "curlx/win32-fopen.h" /* for curlx_f*() */ #include "curlx/inet_ntop.h" /* for curlx_inet_ntop() */ #include "curlx/inet_pton.h" /* for curlx_inet_pton() */ #include "curlx/strcopy.h" /* for curlx_strcopy() */ diff --git a/tests/perf/Makefile.inc b/tests/perf/Makefile.inc index fb1d8982184a..1cffad5200c9 100644 --- a/tests/perf/Makefile.inc +++ b/tests/perf/Makefile.inc @@ -35,7 +35,7 @@ UTILS_H = CURLX_C = \ ../../lib/curlx/base64.c \ - ../../lib/curlx/fopen.c \ + ../../lib/curlx/win32-fopen.c \ ../../lib/curlx/multibyte.c \ ../../lib/curlx/strparse.c \ ../../lib/curlx/timeval.c \ diff --git a/tests/perf/first.h b/tests/perf/first.h index 58990646a07b..5c6bd4582dd8 100644 --- a/tests/perf/first.h +++ b/tests/perf/first.h @@ -33,7 +33,7 @@ #include "curl_setup.h" #include "curlx/base64.h" /* for curlx_base64* */ -#include "curlx/fopen.h" /* for curlx_f*() */ +#include "curlx/win32-fopen.h" /* for curlx_f*() */ #include "curlx/strparse.h" /* for curlx_str_* parsing functions */ #include "curlx/timeval.h" /* for curlx_now type and related functions */ diff --git a/tests/server/Makefile.inc b/tests/server/Makefile.inc index 33a578b8594a..e72ae5d75f54 100644 --- a/tests/server/Makefile.inc +++ b/tests/server/Makefile.inc @@ -35,7 +35,7 @@ UTILS_H = CURLX_C = \ ../../lib/curlx/base64.c \ - ../../lib/curlx/fopen.c \ + ../../lib/curlx/win32-fopen.c \ ../../lib/curlx/inet_ntop.c \ ../../lib/curlx/inet_pton.c \ ../../lib/curlx/multibyte.c \ diff --git a/tests/server/first.h b/tests/server/first.h index 1de9af74b793..ff8851f9cdff 100644 --- a/tests/server/first.h +++ b/tests/server/first.h @@ -63,7 +63,7 @@ extern const struct entry_s s_entries[]; #endif #include "curlx/base64.h" /* for curlx_base64* */ -#include "curlx/fopen.h" /* for curlx_f*() */ +#include "curlx/win32-fopen.h" /* for curlx_f*() */ #include "curlx/inet_ntop.h" /* for curlx_inet_ntop() */ #include "curlx/inet_pton.h" /* for curlx_inet_pton() */ #include "curlx/nonblock.h" /* for curlx_nonblock() */ From 1f2b39cfb15ff08d545b59e505dc7971780f44a4 Mon Sep 17 00:00:00 2001 From: Daniel Stenberg Date: Sat, 5 Sep 2026 14:17:59 +0200 Subject: [PATCH 051/481] getparam: switch off CONNECT headers when using -J To prevent it getting trigged by proxies until we get a header callback that gives us this information. Add test4656 to verify - disabled by default as I can't get it to work reliably Reported-by: Stanislav Fort Closes #22838 --- src/tool_getparam.c | 1 + tests/data/DISABLED | 3 ++ tests/data/Makefile.am | 1 + tests/data/test4656 | 86 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 91 insertions(+) create mode 100644 tests/data/test4656 diff --git a/src/tool_getparam.c b/src/tool_getparam.c index 4694bf3c90bc..1d2a70b97387 100644 --- a/src/tool_getparam.c +++ b/src/tool_getparam.c @@ -2149,6 +2149,7 @@ static ParameterError opt_bool(struct OperationConfig *config, break; case C_REMOTE_HEADER_NAME: /* --remote-header-name */ config->content_disposition = toggle; + config->suppress_connect_headers = toggle; break; case C_INSECURE: /* --insecure */ config->insecure_ok = toggle; diff --git a/tests/data/DISABLED b/tests/data/DISABLED index d4c90a0df913..f24187750d33 100644 --- a/tests/data/DISABLED +++ b/tests/data/DISABLED @@ -63,3 +63,6 @@ 2047 %endif %endif +# We can't close down the TLS connection nicely enough to make this work +# reliably +4656 diff --git a/tests/data/Makefile.am b/tests/data/Makefile.am index a7e40b9fb7b0..7af349984245 100644 --- a/tests/data/Makefile.am +++ b/tests/data/Makefile.am @@ -2101,6 +2101,7 @@ TESTCASES = \ test4000 \ test4001 \ test4644 \ + test4656 \ test4657 \ test5000 \ test5001 \ diff --git a/tests/data/test4656 b/tests/data/test4656 new file mode 100644 index 000000000000..54aedb72f014 --- /dev/null +++ b/tests/data/test4656 @@ -0,0 +1,86 @@ + + + + +HTTP CONNECT +HTTP proxy + + + +# Server-side + + + +HTTP/1.1 301 OK +Location: %TESTNUMBER0002 +Content-Length: 0 +Connection: close + + + + +HTTP/1.1 200 OK swsclose +Content-Length: 18 + +final destination + + + +HTTP/1.1 200 welcome dear +Date: Tue, 09 Nov 2010 14:49:00 GMT +Content-Disposition: attachment; filename="proxy-chosen.bin" + + + + + +# Client-side + + +IPv6 +proxy + + +http-proxy +http +https + + +HTTPS over proxy with CONNECT redirected and -OJ + + +https://remotesite:%HTTPSPORT/%TESTNUMBER -L -O -J -x %HOSTIP:%PROXYPORT --insecure --output-dir=%LOGDIR + + + +# Verify data after the test has been "shot" + + +CONNECT remotesite:%HTTPSPORT HTTP/1.1 +Host: remotesite:%HTTPSPORT +User-Agent: curl/%VERSION +Proxy-Connection: Keep-Alive + +CONNECT remotesite:%HTTPSPORT HTTP/1.1 +Host: remotesite:%HTTPSPORT +User-Agent: curl/%VERSION +Proxy-Connection: Keep-Alive + + + +GET /%TESTNUMBER HTTP/1.1 +Host: remotesite:%HTTPSPORT +User-Agent: curl/%VERSION +Accept: */* + +GET /%TESTNUMBER0002 HTTP/1.1 +Host: remotesite:%HTTPSPORT +User-Agent: curl/%VERSION +Accept: */* + + + +final destination + + + From 19d0f4acfa9df373e8e1f987c11b7165e1edac34 Mon Sep 17 00:00:00 2001 From: Fredrik Blau <40271584+fredrikblau@users.noreply.github.com> Date: Wed, 2 Sep 2026 18:51:40 +0330 Subject: [PATCH 052/481] tool: (re)set SSL verification in --libcurl output So that it shows it correctly even if toggled on/off between --next. Fixes #22701 Closes #22745 --- src/config2setopts.c | 12 ++++--- src/tool_easysrc.c | 9 +++++ src/tool_easysrc.h | 2 ++ src/tool_setopt.c | 10 ++++++ src/tool_setopt.h | 4 +++ tests/data/Makefile.am | 2 ++ tests/data/data1679.c | 80 ++++++++++++++++++++++++++++++++++++++++++ tests/data/test1679 | 59 +++++++++++++++++++++++++++++++ 8 files changed, 173 insertions(+), 5 deletions(-) create mode 100644 tests/data/data1679.c create mode 100644 tests/data/test1679 diff --git a/src/config2setopts.c b/src/config2setopts.c index 1c823f16ae60..b64455e4ae06 100644 --- a/src/config2setopts.c +++ b/src/config2setopts.c @@ -409,11 +409,13 @@ static CURLcode ssl_setopts(struct OperationConfig *config, CURL *curl) MY_SETOPT_STR(curl, CURLOPT_SSLKEYTYPE, config->key_type); MY_SETOPT_STR(curl, CURLOPT_PROXY_SSLKEYTYPE, config->proxy_key_type); - /* libcurl default is strict verifyhost -> 1L, verifypeer -> 1L */ - if(config->insecure_ok) { - my_setopt_long(curl, CURLOPT_SSL_VERIFYPEER, 0); - my_setopt_long(curl, CURLOPT_SSL_VERIFYHOST, 0); - } + /* libcurl default is strict verifyhost -> 1L, verifypeer -> 1L. Set + these for every operation so --libcurl output does not retain + --insecure from a preceding --next operation. */ + my_setopt_long_force(curl, CURLOPT_SSL_VERIFYPEER, + config->insecure_ok ? 0 : 1); + my_setopt_long_force(curl, CURLOPT_SSL_VERIFYHOST, + config->insecure_ok ? 0 : 1); if(config->doh_insecure_ok) { my_setopt_long(curl, CURLOPT_DOH_SSL_VERIFYPEER, 0); diff --git a/src/tool_easysrc.c b/src/tool_easysrc.c index c2a34c3b2b1e..d7862fcb2561 100644 --- a/src/tool_easysrc.c +++ b/src/tool_easysrc.c @@ -40,6 +40,7 @@ struct slist_wc *easysrc_toohard; /* Unconvertible setopt */ struct slist_wc *easysrc_clean; /* Clean up allocated data */ int easysrc_mime_count; int easysrc_slist_count; +static bool easysrc_performed; static const char * const srchead[] = { "/********* Sample code generated by the curl command line tool **********", @@ -121,9 +122,15 @@ CURLcode easysrc_addf(struct slist_wc **plist, const char *fmt, ...) CURLcode easysrc_init(void) { + easysrc_performed = FALSE; return easysrc_add(&easysrc_code, "curl = curl_easy_init();"); } +bool easysrc_is_first(void) +{ + return !easysrc_performed; +} + CURLcode easysrc_perform(void) { CURLcode result = CURLE_OK; @@ -155,6 +162,8 @@ CURLcode easysrc_perform(void) result = easysrc_add(&easysrc_code, "result = curl_easy_perform(curl);"); if(!result) result = easysrc_add(&easysrc_code, ""); + if(!result) + easysrc_performed = TRUE; return result; } diff --git a/src/tool_easysrc.h b/src/tool_easysrc.h index f37ce4ba86f5..642ea33e8cc0 100644 --- a/src/tool_easysrc.h +++ b/src/tool_easysrc.h @@ -44,6 +44,7 @@ extern CURLcode easysrc_addf(struct slist_wc **plist, const char *fmt, ...) CURL_PRINTF(2, 3); extern CURLcode easysrc_perform(void); extern CURLcode easysrc_cleanup(void); +extern bool easysrc_is_first(void); void dumpeasysrc(void); #else /* CURL_DISABLE_LIBCURL_OPTION is defined */ @@ -52,6 +53,7 @@ void dumpeasysrc(void); #define easysrc_cleanup() #define dumpeasysrc(x) #define easysrc_perform() CURLE_OK +#define easysrc_is_first() TRUE #endif /* CURL_DISABLE_LIBCURL_OPTION */ diff --git a/src/tool_setopt.c b/src/tool_setopt.c index 51e22756aa4a..cd41d8d6b87a 100644 --- a/src/tool_setopt.c +++ b/src/tool_setopt.c @@ -631,6 +631,16 @@ CURLcode tool_setopt_long(CURL *curl, const char *name, CURLoption tag, return result; } +CURLcode tool_setopt_long_force(CURL *curl, const char *name, CURLoption tag, + long lval) +{ + CURLcode result = curl_easy_setopt(curl, tag, lval); + if(global->libcurl && !result && (!easysrc_is_first() || lval == 0)) + result = easysrc_addf(&easysrc_code, "curl_easy_setopt(curl, %s, %ldL);", + name, lval); + return result; +} + /* options that set curl_off_t */ CURLcode tool_setopt_offt(CURL *curl, const char *name, CURLoption tag, curl_off_t lval) diff --git a/src/tool_setopt.h b/src/tool_setopt.h index a737fcddf54f..6414c142887b 100644 --- a/src/tool_setopt.h +++ b/src/tool_setopt.h @@ -92,6 +92,8 @@ CURLcode tool_setopt_slist(CURL *curl, const char *name, CURLoption tag, struct curl_slist *list); CURLcode tool_setopt_long(CURL *curl, const char *name, CURLoption tag, long lval); +CURLcode tool_setopt_long_force(CURL *curl, const char *name, CURLoption tag, + long lval); CURLcode tool_setopt_offt(CURL *curl, const char *name, CURLoption tag, curl_off_t lval); CURLcode tool_setopt_str(CURL *curl, struct OperationConfig *config, @@ -100,6 +102,7 @@ CURLcode tool_setopt_str(CURL *curl, struct OperationConfig *config, CURLcode tool_setopt_ptr(CURL *curl, const char *name, CURLoption tag, ...); #define my_setopt_long(x, y, z) tool_setopt_long(x, #y, y, z) +#define my_setopt_long_force(x, y, z) tool_setopt_long_force(x, #y, y, z) #define my_setopt_offt(x, y, z) tool_setopt_offt(x, #y, y, z) #define my_setopt_ptr(x, y, z) tool_setopt_ptr(x, #y, y, z) #define my_setopt_str(x, y, z) tool_setopt_str(x, config, #y, y, z) @@ -125,6 +128,7 @@ CURLcode tool_setopt_ptr(CURL *curl, const char *name, CURLoption tag, ...); /* No --libcurl, so pass options directly to library */ #define my_setopt_long(x, y, z) curl_easy_setopt(x, y, (long)(z)) +#define my_setopt_long_force(x, y, z) curl_easy_setopt(x, y, (long)(z)) #define my_setopt_offt(x, y, z) curl_easy_setopt(x, y, (curl_off_t)(z)) #define my_setopt_ptr(x, y, z) curl_easy_setopt(x, y, z) #define my_setopt_str(x, y, z) curl_easy_setopt(x, y, z) diff --git a/tests/data/Makefile.am b/tests/data/Makefile.am index 7af349984245..674d88007979 100644 --- a/tests/data/Makefile.am +++ b/tests/data/Makefile.am @@ -1700,6 +1700,7 @@ TESTCASES = \ test1676 \ test1677 \ test1678 \ + test1679 \ test1680 \ test1681 \ test1682 \ @@ -2151,6 +2152,7 @@ EXTRA_DIST = \ data1463.txt \ data1465.c \ data1481.c \ + data1679.c \ data1705-1.md \ data1705-2.md \ data1705-3.md \ diff --git a/tests/data/data1679.c b/tests/data/data1679.c new file mode 100644 index 000000000000..22144d18a5e3 --- /dev/null +++ b/tests/data/data1679.c @@ -0,0 +1,80 @@ +/********* Sample code generated by the curl command line tool ********** + * All curl_easy_setopt() options are documented at: + * https://curl.se/libcurl/c/curl_easy_setopt.html + ************************************************************************/ +#include + +int main(int argc, char *argv[]) +{ + CURLcode result; + CURL *curl; + + curl = curl_easy_init(); + curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L); + curl_easy_setopt(curl, CURLOPT_BUFFERSIZE, 102400L); + curl_easy_setopt(curl, CURLOPT_URL, "http://%HOSTIP:%HTTPPORT/we/want/%TESTNUMBER"); + curl_easy_setopt(curl, CURLOPT_USERAGENT, "curl/%VERSION"); + curl_easy_setopt(curl, CURLOPT_MAXREDIRS, 50L); + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L); + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L); + curl_easy_setopt(curl, CURLOPT_SSLVERSION, (long)CURL_SSLVERSION_TLSv1_2); + curl_easy_setopt(curl, CURLOPT_TCP_KEEPALIVE, 1L); + + /* Here is a list of options the curl code used that cannot get generated + as source easily. You may choose to either not use them or implement + them yourself. + + CURLOPT_DEBUGFUNCTION was set to a function pointer + CURLOPT_DEBUGDATA was set to an object pointer + CURLOPT_WRITEDATA was set to an object pointer + CURLOPT_WRITEFUNCTION was set to a function pointer + CURLOPT_READDATA was set to an object pointer + CURLOPT_READFUNCTION was set to a function pointer + CURLOPT_SEEKDATA was set to an object pointer + CURLOPT_SEEKFUNCTION was set to a function pointer + CURLOPT_HEADERFUNCTION was set to a function pointer + CURLOPT_HEADERDATA was set to an object pointer + CURLOPT_ERRORBUFFER was set to an object pointer + CURLOPT_STDERR was set to an object pointer + + */ + + result = curl_easy_perform(curl); + + curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L); + curl_easy_setopt(curl, CURLOPT_BUFFERSIZE, 102400L); + curl_easy_setopt(curl, CURLOPT_URL, "http://%HOSTIP:%HTTPPORT/we/want/%TESTNUMBER"); + curl_easy_setopt(curl, CURLOPT_USERAGENT, "curl/%VERSION"); + curl_easy_setopt(curl, CURLOPT_MAXREDIRS, 50L); + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1L); + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 1L); + curl_easy_setopt(curl, CURLOPT_SSLVERSION, (long)CURL_SSLVERSION_TLSv1_2); + curl_easy_setopt(curl, CURLOPT_TCP_KEEPALIVE, 1L); + + /* Here is a list of options the curl code used that cannot get generated + as source easily. You may choose to either not use them or implement + them yourself. + + CURLOPT_DEBUGFUNCTION was set to a function pointer + CURLOPT_DEBUGDATA was set to an object pointer + CURLOPT_WRITEDATA was set to an object pointer + CURLOPT_WRITEFUNCTION was set to a function pointer + CURLOPT_READDATA was set to an object pointer + CURLOPT_READFUNCTION was set to a function pointer + CURLOPT_SEEKDATA was set to an object pointer + CURLOPT_SEEKFUNCTION was set to a function pointer + CURLOPT_HEADERFUNCTION was set to a function pointer + CURLOPT_HEADERDATA was set to an object pointer + CURLOPT_ERRORBUFFER was set to an object pointer + CURLOPT_STDERR was set to an object pointer + + */ + + result = curl_easy_perform(curl); + + curl_easy_cleanup(curl); + curl = NULL; + + return (int)result; +} +/**** End of sample code ****/ diff --git a/tests/data/test1679 b/tests/data/test1679 new file mode 100644 index 000000000000..a3d70e67a247 --- /dev/null +++ b/tests/data/test1679 @@ -0,0 +1,59 @@ + + + + +HTTP +--libcurl +--next +--insecure + + + + + +HTTP/1.1 200 OK +Content-Length: 0 +Connection: close + + + + + + +http +SSL +--libcurl + + +http + + +--libcurl resets SSL verification after --next + + +http://%HOSTIP:%HTTPPORT/we/want/%TESTNUMBER --libcurl %LOGDIR/test%TESTNUMBER.c --insecure --next http://%HOSTIP:%HTTPPORT/we/want/%TESTNUMBER + + + + + +GET /we/want/%TESTNUMBER HTTP/1.1 +Host: %HOSTIP:%HTTPPORT +User-Agent: curl/%VERSION +Accept: */* + +GET /we/want/%TESTNUMBER HTTP/1.1 +Host: %HOSTIP:%HTTPPORT +User-Agent: curl/%VERSION +Accept: */* + + + +# CURLOPT_INTERLEAVEDATA requires RTSP protocol +$_ = '' if /CURLOPT_INTERLEAVEDATA/ + + +%includetext %SRCDIR/data/data1679.c% + + + From ddeb34a2178211fa559657c8ffc625a468a7803e Mon Sep 17 00:00:00 2001 From: Daniel Stenberg Date: Sat, 5 Sep 2026 23:05:20 +0200 Subject: [PATCH 053/481] RELEASE-NOTES: synced --- RELEASE-NOTES | 75 ++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 69 insertions(+), 6 deletions(-) diff --git a/RELEASE-NOTES b/RELEASE-NOTES index 456bbf07ec49..35b65f52ed0f 100644 --- a/RELEASE-NOTES +++ b/RELEASE-NOTES @@ -4,21 +4,52 @@ curl and libcurl 8.22.1 Command line options: 278 curl_easy_setopt() options: 312 Public functions in libcurl: 100 - Authors: 1520 - Contributors: 3790 + Authors: 1524 + Contributors: 3795 This release includes the following changes: + o tool_cb_wrt: inject the no-clobber number before the extension [21] + o tool_cb_wrt: try up to .9999 when no-clobbering [46] This release includes the following bugfixes: + o build: drop `HTTP_ONLY` option [23] + o clang-tidy: merge nested `if()`s [39] + o connectdata: use less curltime [34] + o cookie: ignore individual cookie errors when loading [6] + o curl_addrinfo: drop redundant parentheses [41] + o CURLOPT*ISSUERCERT*: not verified cryptographically [28] o CURLOPT_SHARE.md: remove bad explanation for cookie sharing [7] + o digest: check peer equality for staleness [24] + o digest: fix maximum length quoted value parsing [22] + o digest: parse without storing 'value' in local buffer [20] o docs: add the missing .html to the url-encode book link [3] o doh: reject undersized HTTPS RDATA [11] + o escape: polish encoding and decoding for speed [37] + o ftp: tighten use_ssl check for conn reuse [19] + o getparam: switch off CONNECT headers when using -J [4] + o global_init: tidy up, simplify, fix issues [43] + o HTTP-CONNECT: do not react to 401 responses [31] + o http: reject resumed uploads [16] + o mprintf: stage output and emit it in runs [38] + o multi: reduce struct size with small cleanups [25] + o multi: remove redundant completion message list [32] + o multi: store timeout epoch as whole seconds [36] + o multi_ntfy: replace enabled bitset with inline flags [44] + o multihandle: some easy shrinks [33] o ngtcp2: shrink max sendbuf [13] + o openssl/gtls: reject CRLfile with native CA store, matching rustls [27] + o ossl_verifyhost: remove assumption of null termination of ASN1_STRING. [26] + o runtests: Added runnernums in order to have O(1) lookup [45] + o schannel: auto_client_cert conn matching [17] + o schannel: clear borrowed sslContext on close [15] o test1399: improve reliability [9] + o tool: (re)set SSL verification in --libcurl output [1] + o uint-bset: skip unused slots when counting o vquic: fix preprocessor check for old Darwin versions [8] o vtls: fix Curl_cert_hostcheck when the pattern is not a C string [12] + o warnless: dedupe Intel compiler warning suppression pragmas [42] o x509: stricter parsing [2] This release includes the following known bugs: @@ -41,18 +72,50 @@ Planned upcoming removals include: This release would not have looked like this without help, code, reports and advice from friends like these: - Artem Prilutskiy, Christian Hesse, Daniel Lang, Daniel Stenberg, - David Benjamin, Max Dymond, Ray Satiro, Roshan Ramani, Stefan Eissing, - Un1q32 - (10 contributors) + Artem Prilutskiy, Aviv Engelberg, Christian Hesse, Daniel Lang, + Daniel Stenberg, David Benjamin, Fredrik Blau, Huang Yiheng, Joshua Rogers, + Martin Dukek, Max Dymond, Mihai-Gabriel Marin, Ray Satiro, renovate[bot], + Ron Kuper, Roshan Ramani, Rudolf Polzer, Stanislav Fort, Stefan Eissing, + Un1q32, Viktor Szakats + (21 contributors) References to bug reports and discussions on issues: + [1] = https://curl.se/bug/?i=22701 [2] = https://curl.se/bug/?i=22799 [3] = https://curl.se/bug/?i=22811 + [4] = https://curl.se/bug/?i=22838 + [6] = https://curl.se/bug/?i=22840 [7] = https://curl.se/bug/?i=22788 [8] = https://curl.se/bug/?i=22797 [9] = https://curl.se/bug/?i=22798 [11] = https://curl.se/bug/?i=22791 [12] = https://curl.se/bug/?i=22795 [13] = https://curl.se/bug/?i=22786 + [15] = https://curl.se/bug/?i=22720 + [16] = https://curl.se/bug/?i=22703 + [17] = https://curl.se/bug/?i=22827 + [19] = https://curl.se/bug/?i=22823 + [20] = https://curl.se/bug/?i=22831 + [21] = https://curl.se/bug/?i=22787 + [22] = https://curl.se/bug/?i=22830 + [23] = https://curl.se/bug/?i=22740 + [24] = https://curl.se/bug/?i=22828 + [25] = https://curl.se/bug/?i=22807 + [26] = https://curl.se/bug/?i=22822 + [27] = https://curl.se/bug/?i=22826 + [28] = https://curl.se/bug/?i=22825 + [31] = https://curl.se/bug/?i=22817 + [32] = https://curl.se/bug/?i=22805 + [33] = https://curl.se/bug/?i=22801 + [34] = https://curl.se/bug/?i=22815 + [36] = https://curl.se/bug/?i=22806 + [37] = https://curl.se/bug/?i=22780 + [38] = https://curl.se/bug/?i=22766 + [39] = https://curl.se/bug/?i=22793 + [41] = https://curl.se/bug/?i=22810 + [42] = https://curl.se/bug/?i=22796 + [43] = https://curl.se/bug/?i=22746 + [44] = https://curl.se/bug/?i=22804 + [45] = https://curl.se/bug/?i=22753 + [46] = https://curl.se/bug/?i=22784 From 83b7ad083d4252c11e7e897436ef073029c65de0 Mon Sep 17 00:00:00 2001 From: Daniel Stenberg Date: Sat, 5 Sep 2026 17:33:40 +0200 Subject: [PATCH 054/481] curl_fopen: restore the uid and gid checks They were brought by 20f9dd6bae but were unfortunately degraded over time. Only clone the original properties if the new file has the same uid and gid. Reported-by: Stanislav Fort Assisted-by: Viktor Szakats Closes #22843 --- CMake/unix-cache.cmake | 1 + CMakeLists.txt | 1 + configure.ac | 1 + lib/curl_config-cmake.h.in | 3 +++ lib/curl_fopen.c | 23 ++++++++++++++--------- 5 files changed, 20 insertions(+), 9 deletions(-) diff --git a/CMake/unix-cache.cmake b/CMake/unix-cache.cmake index 8ec2cd48c193..36c75e7daa0e 100644 --- a/CMake/unix-cache.cmake +++ b/CMake/unix-cache.cmake @@ -80,6 +80,7 @@ if((APPLE AND CMAKE_OSX_DEPLOYMENT_TARGET VERSION_GREATER_EQUAL 10.9) OR elseif(NOT APPLE) set(HAVE_MEMSET_S 0) endif() +set(HAVE_FCHMOD 1) set(HAVE_FCNTL 1) set(HAVE_FCNTL_H 1) set(HAVE_FCNTL_O_NONBLOCK 1) diff --git a/CMakeLists.txt b/CMakeLists.txt index 29f8c67cf41c..5fb2e22adb06 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1669,6 +1669,7 @@ endif() if(NOT WIN32) check_include_file("dirent.h" HAVE_DIRENT_H) + check_function_exists("fchmod" HAVE_FCHMOD) check_function_exists("gettimeofday" HAVE_GETTIMEOFDAY) # sys/time.h check_function_exists("if_nametoindex" HAVE_IF_NAMETOINDEX) # net/if.h check_function_exists("realpath" HAVE_REALPATH) diff --git a/configure.ac b/configure.ac index 4c4fac2e2ba3..9a382c321e1a 100644 --- a/configure.ac +++ b/configure.ac @@ -4249,6 +4249,7 @@ fi if test "$curl_cv_native_windows" != "yes"; then AC_CHECK_FUNCS([\ + fchmod \ gettimeofday \ if_nametoindex \ realpath \ diff --git a/lib/curl_config-cmake.h.in b/lib/curl_config-cmake.h.in index d25eab14ecca..b316c0fb39ad 100644 --- a/lib/curl_config-cmake.h.in +++ b/lib/curl_config-cmake.h.in @@ -206,6 +206,9 @@ /* Define to 1 if you have the `accept4' function. */ #cmakedefine HAVE_ACCEPT4 1 +/* Define to 1 if you have the `fchmod' function. */ +#cmakedefine HAVE_FCHMOD 1 + /* Define to 1 if you have the `fnmatch' function. */ #cmakedefine HAVE_FNMATCH 1 diff --git a/lib/curl_fopen.c b/lib/curl_fopen.c index 75054ffb77bf..13e1fe4d28b6 100644 --- a/lib/curl_fopen.c +++ b/lib/curl_fopen.c @@ -102,13 +102,6 @@ CURLcode Curl_fopen(struct Curl_easy *data, const char *filename, return CURLE_OK; } curlx_fclose(*fh); -#ifdef HAVE_GETEUID - /* If the existing file is not owned by the user, do not inherit - * its permissions at the temp file created below. The permissions - * might be unsuitable for holding user private data. */ - if(sb.st_uid != geteuid()) - sb.st_mode = 0; -#endif #endif /* !_WIN32 */ *fh = NULL; @@ -136,14 +129,26 @@ CURLcode Curl_fopen(struct Curl_easy *data, const char *filename, #elif (defined(ANDROID) || defined(__ANDROID__)) && \ (defined(__i386__) || defined(__arm__)) fd = curlx_open(tempstore, O_WRONLY | O_CREAT | O_EXCL, - (mode_t)(S_IRUSR | S_IWUSR | sb.st_mode)); + (mode_t)(S_IRUSR | S_IWUSR | 0600)); #else fd = curlx_open(tempstore, O_WRONLY | O_CREAT | O_EXCL, - S_IRUSR | S_IWUSR | sb.st_mode); + S_IRUSR | S_IWUSR | 0600); #endif if(fd == -1) goto fail; +#ifdef HAVE_FCHMOD + { + curlx_struct_stat nsb; + if((curlx_fstat(fd, &nsb) != -1) && + (nsb.st_uid == sb.st_uid) && (nsb.st_gid == sb.st_gid)) { + /* if the user and group are the same, clone the original mode */ + if(fchmod(fd, sb.st_mode) == -1) + goto fail; + } + } +#endif + *fh = curlx_fdopen(fd, FOPEN_WRITETEXT); if(!*fh) goto fail; From 68ed69a5ab668ff6bbd7ff31f6deb40dee59642e Mon Sep 17 00:00:00 2001 From: Viktor Szakats Date: Mon, 29 Jun 2026 16:21:10 +0200 Subject: [PATCH 055/481] openssl: use non-deprecated API for MD5/MD4 Rework MD4 and MD5 code to use the modern `EVP_Digest_*()` API, replacing the deprecated `MD4_*()` and `MD5_*()` ones. It allows to use these OpenSSL crypto primitives with OpenSSL built with the `no-deprecated` option. Closes #22221 --- lib/curl_setup.h | 4 ++-- lib/md4.c | 35 +++++++++++++++++++++++++++-------- lib/md5.c | 30 ++++++++++++++++++++---------- 3 files changed, 49 insertions(+), 20 deletions(-) diff --git a/lib/curl_setup.h b/lib/curl_setup.h index e059cc198373..499cdc82da04 100644 --- a/lib/curl_setup.h +++ b/lib/curl_setup.h @@ -1518,8 +1518,8 @@ typedef struct sockaddr_un { /* OpenSSL 3 marks these functions deprecated but we have no replacements (yet) so tell the compiler to not warn for them: - DES_* (for NTLM) - - EVP_PKEY_get1_RSA, MD5_*, RSA_flags, RSA_free (auto-skipped for OpenSSL - built with no-deprecated) */ + - EVP_PKEY_get1_RSA, RSA_flags, RSA_free (auto-skipped for OpenSSL built + with no-deprecated) */ # define OPENSSL_SUPPRESS_DEPRECATED # ifdef _WIN32 /* Silence LibreSSL warnings about wincrypt.h collision. Works in 3.8.2+ */ diff --git a/lib/md4.c b/lib/md4.c index e030ffac30c2..06cfbb0b3e4a 100644 --- a/lib/md4.c +++ b/lib/md4.c @@ -42,16 +42,35 @@ #include #endif -/* When OpenSSL or wolfSSL is available, we use their MD4 functions. */ - #if defined(USE_OPENSSL) && !defined(OPENSSL_NO_MD4) -#include +#include + +typedef EVP_MD_CTX *my_md4_ctx; + +static int my_md4_init(my_md4_ctx *ctx) +{ + *ctx = EVP_MD_CTX_new(); + if(!*ctx) + return 0; + + if(!EVP_DigestInit_ex(*ctx, EVP_md4(), NULL)) { + EVP_MD_CTX_free(*ctx); + return 0; + } + return 1; +} -/* Map directly to OpenSSL implementation */ -#define my_md4_ctx MD4_CTX -#define my_md4_init MD4_Init -#define my_md4_update MD4_Update -#define my_md4_final MD4_Final +static void my_md4_update(my_md4_ctx *ctx, + const unsigned char *input, unsigned int len) +{ + (void)EVP_DigestUpdate(*ctx, input, len); +} + +static void my_md4_final(unsigned char *digest, my_md4_ctx *ctx) +{ + (void)EVP_DigestFinal_ex(*ctx, digest, NULL); + EVP_MD_CTX_free(*ctx); +} #elif defined(USE_WOLFSSL) && !defined(NO_MD4) #include diff --git a/lib/md5.c b/lib/md5.c index f842c7ae63b4..42f5d04e889e 100644 --- a/lib/md5.c +++ b/lib/md5.c @@ -72,29 +72,39 @@ static void my_md5_final(unsigned char *digest, void *ctx) #endif } -#elif defined(USE_OPENSSL) && \ - !defined(OPENSSL_NO_MD5) && !defined(OPENSSL_NO_DEPRECATED_3_0) -#include +#elif defined(USE_OPENSSL) && !defined(OPENSSL_NO_MD5) +#include -typedef MD5_CTX my_md5_ctx; +typedef EVP_MD_CTX *my_md5_ctx; -static CURLcode my_md5_init(void *ctx) +static CURLcode my_md5_init(void *in) { - if(!MD5_Init(ctx)) + EVP_MD_CTX **ctx = (EVP_MD_CTX **)in; + *ctx = EVP_MD_CTX_new(); + if(!*ctx) return CURLE_OUT_OF_MEMORY; + if(!EVP_DigestInit_ex(*ctx, EVP_md5(), NULL)) { + EVP_MD_CTX_free(*ctx); + *ctx = NULL; + return CURLE_FAILED_INIT; + } return CURLE_OK; } -static void my_md5_update(void *ctx, +static void my_md5_update(void *in, const unsigned char *input, unsigned int len) { - (void)MD5_Update(ctx, input, len); + EVP_MD_CTX **ctx = (EVP_MD_CTX **)in; + (void)EVP_DigestUpdate(*ctx, input, len); } -static void my_md5_final(unsigned char *digest, void *ctx) +static void my_md5_final(unsigned char *digest, void *in) { - (void)MD5_Final(digest, ctx); + EVP_MD_CTX **ctx = (EVP_MD_CTX **)in; + (void)EVP_DigestFinal_ex(*ctx, digest, NULL); + EVP_MD_CTX_free(*ctx); + *ctx = NULL; } #elif defined(USE_WOLFSSL) && !defined(NO_MD5) From 5a50313147292a8232b35457a52c7b65bc2517c3 Mon Sep 17 00:00:00 2001 From: Jay Satiro Date: Mon, 24 Aug 2026 03:53:23 -0400 Subject: [PATCH 056/481] appveyor: save an artifact for the vs2026 debug build - Save an artifact (zip of curl.exe and dependencies) for the job 'CM VS2026, Debug, x64, OpenSSL 3.6 + Schannel, Static, Unicode, Build-tests & examples, clang-cl'. For user testing. Ref: https://github.com/curl/curl/issues/22466#issuecomment-5389439586 Closes https://github.com/curl/curl/pull/22653 --- appveyor.sh | 24 ++++++++++++++++++++++++ appveyor.yml | 1 + 2 files changed, 25 insertions(+) diff --git a/appveyor.sh b/appveyor.sh index 64e8b6d13182..8fce7859f525 100644 --- a/appveyor.sh +++ b/appveyor.sh @@ -114,6 +114,30 @@ else echo "Skip running curl.exe. Reason: ${SKIP_RUN}" fi +# create artifact + +if [[ "${CREATE_ARTIFACT:-}" = 'yes' ]]; then + cp /usr/ssl/certs/ca-bundle.crt curl-ca-bundle.crt + echo "Checking that https works (it should find curl-ca-bundle.crt if needed)" + "${curl}" -v -fsS --retry 6 --retry-all-errors -o /dev/null https://curl.se + if [ -n "${APPVEYOR_PULL_REQUEST_NUMBER:-}" ]; then + archive="curl_pr${APPVEYOR_PULL_REQUEST_NUMBER}_${APPVEYOR_PULL_REQUEST_HEAD_COMMIT}.zip" + else + archive="curl_${APPVEYOR_REPO_COMMIT}.zip" + fi +cat << EOF > WARNING.txt +WARNING: Do not run this build unless the download link was provided by staff. +Anyone can submit a PR with possibly malicious code to generate a build. +${archive} +EOF + echo "Finding curl module dependencies" + "${curl}" --dump-module-paths | grep -Fv "C:\Windows" | tee > files.tmp + echo "Creating artifact" + 7z a "${archive}" -y -bb1 -bsp0 -mx9 -tzip -i@files.tmp curl-ca-bundle.crt WARNING.txt + rm files.tmp + appveyor PushArtifact "${archive}" +fi + # build tests if [ -n "${CMAKE_GENERATOR:-}" ] && [[ "${APPVEYOR_JOB_NAME}" = *'Build-tests'* ]]; then diff --git a/appveyor.yml b/appveyor.yml index 95c081d31540..e6231e6a7e8e 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -94,6 +94,7 @@ environment: CMAKE_SHA256: dfc2b2afac257555e3b9ce375b12b2883964283a366c17fec96cf4d17e4f1677 CMAKE_GENERATOR: 'Visual Studio 18 2026' CMAKE_GENERATE: '-A x64 -T ClangCl -DBUILD_SHARED_LIBS=OFF -DCURL_USE_OPENSSL=ON -DENABLE_UNICODE=ON' + CREATE_ARTIFACT: yes - job_name: 'CM VS2022, Release, x64, Schannel, Shared, Unicode, Build-tests' APPVEYOR_BUILD_WORKER_IMAGE: 'Visual Studio 2022' From 6ae9befb2fb6bdfdb5fc11fdf76bbe519fe1f52c Mon Sep 17 00:00:00 2001 From: Daniel Stenberg Date: Sat, 5 Sep 2026 15:55:58 +0200 Subject: [PATCH 057/481] config2setopts: always set the security options for SCP/SFTP If the protocols are built-in Closes #22839 --- src/config2setopts.c | 31 +++++++++++++++++++++---------- tests/data/test1679 | 1 + 2 files changed, 22 insertions(+), 10 deletions(-) diff --git a/src/config2setopts.c b/src/config2setopts.c index b64455e4ae06..b212fb834835 100644 --- a/src/config2setopts.c +++ b/src/config2setopts.c @@ -202,12 +202,11 @@ static CURLcode url_proto_and_rewrite(char **url, return result; } -static CURLcode ssh_setopts(struct OperationConfig *config, CURL *curl, - const char *use_proto) +static CURLcode ssh_setopts(struct OperationConfig *config, CURL *curl) { CURLcode result; - if(use_proto != proto_scp && use_proto != proto_sftp) + if(!proto_scp && !proto_sftp) return CURLE_OK; /* SSH and SSL private key uses same command-line option */ @@ -236,7 +235,25 @@ static CURLcode ssh_setopts(struct OperationConfig *config, CURL *curl, if(!known) return CURLE_OUT_OF_MEMORY; } + else if(!config->hostpubmd5 && !config->hostpubsha256) { + /* couldn't find an existing one, instead set one that MIGHT work as + we don't verify the host using other means */ + struct dynbuf k; + char *home = getenv("HOME"); + curlx_dyn_init(&k, 256); + + /* If the HOME environment variable is missing this renders a path + that is likely to not work but the HOME one also does not exist! + */ + result = curlx_dyn_addf(&k, + "%s" DIR_CHAR ".ssh" DIR_CHAR "known_hosts", + home ? home : ""); + if(result) + return result; + known = curlx_dyn_ptr(&k); + } } + if(known) { result = my_setopt_str(curl, CURLOPT_SSH_KNOWNHOSTS, known); if(result) { @@ -247,12 +264,6 @@ static CURLcode ssh_setopts(struct OperationConfig *config, CURL *curl, /* store it in global to avoid repeated checks */ config->knownhosts = known; } - else if(!config->hostpubmd5 && !config->hostpubsha256) { - errorf("Could not find a known_hosts file"); - return CURLE_FAILED_INIT; - } - else - warnf("Could not find a known_hosts file"); } return CURLE_OK; /* ignore if SHA256 did not work */ } @@ -1037,7 +1048,7 @@ static CURLcode protocol_setopts(struct OperationConfig *config, if(result) return result; - result = ssh_setopts(config, curl, use_proto); + result = ssh_setopts(config, curl); if(setopt_bad(result)) return result; diff --git a/tests/data/test1679 b/tests/data/test1679 index a3d70e67a247..97582d52f973 100644 --- a/tests/data/test1679 +++ b/tests/data/test1679 @@ -51,6 +51,7 @@ Accept: */* # CURLOPT_INTERLEAVEDATA requires RTSP protocol $_ = '' if /CURLOPT_INTERLEAVEDATA/ +$_ = '' if /CURLOPT_SSH_KNOWNHOSTS/ %includetext %SRCDIR/data/data1679.c% From 3d445dd60d52097331eb2859cf7eda90208d0734 Mon Sep 17 00:00:00 2001 From: Ron Kuper Date: Sat, 5 Sep 2026 11:57:50 -0400 Subject: [PATCH 058/481] perf: add maprintf and simpleformat printf tests snprintf.c times curl_msnprintf() into a caller's buffer with a format that uses widths and a '*' argument. Two common shapes are not covered: the allocating output path, and a plain format with the integer types curl uses most. maprintf.c times curl_maprintf(), which allocates and returns the result and is the path libcurl uses to assemble request lines and headers. The format is a request header block: literal text, %s, and a curl_off_t Content-Length, the shape lib/http.c builds on every request with a body. simpleformat.c times curl_msnprintf() on a commonly used simple message: literal text plus %s, a size_t and a curl_off_t, no width or precision, taken from a real infof() line in lib/imap.c. Where snprintf measures an elaborate format, this measures the common one. ./perf maprintf [loops] ./perf simpleformat [loops] checksrc clean; both build through the perf bundle from a clean reconfigure and run alongside the existing tests. Closes #22844 --- tests/perf/Makefile.inc | 2 ++ tests/perf/README.md | 18 ++++++++++ tests/perf/maprintf.c | 75 +++++++++++++++++++++++++++++++++++++++ tests/perf/simpleformat.c | 70 ++++++++++++++++++++++++++++++++++++ 4 files changed, 165 insertions(+) create mode 100644 tests/perf/maprintf.c create mode 100644 tests/perf/simpleformat.c diff --git a/tests/perf/Makefile.inc b/tests/perf/Makefile.inc index 1cffad5200c9..57ab8066fb52 100644 --- a/tests/perf/Makefile.inc +++ b/tests/perf/Makefile.inc @@ -45,6 +45,8 @@ CURLX_C = \ TESTS_C = \ base64dec.c \ base64enc.c \ + maprintf.c \ + simpleformat.c \ snprintf.c \ urldecode.c \ urlencode.c \ diff --git a/tests/perf/README.md b/tests/perf/README.md index 12bef815eae0..95551f600470 100644 --- a/tests/perf/README.md +++ b/tests/perf/README.md @@ -45,6 +45,24 @@ call: ./perf snprintf [loops] +## `maprintf` + +This test repeatedly formats a request header block with `curl_maprintf()`, +which allocates the result, and measures the time per call. It exercises the +allocating output path and a `curl_off_t` conversion, which the `snprintf` +test does not: + + ./perf maprintf [loops] + +## `simpleformat` + +This test repeatedly formats a commonly used simple message with +`curl_msnprintf()`: literal text plus `%s`, a `size_t` and a `curl_off_t`, +with no width or precision. Where `snprintf` measures an elaborate format, +this measures the common one: + + ./perf simpleformat [loops] + ## `urlencode` This test URL (percent) encodes a 256-byte buffer that has every different diff --git a/tests/perf/maprintf.c b/tests/perf/maprintf.c new file mode 100644 index 000000000000..f9527bcf147c --- /dev/null +++ b/tests/perf/maprintf.c @@ -0,0 +1,75 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ +#include "first.h" + +/* snprintf.c times curl_msnprintf() into a caller's buffer. This test times + curl_maprintf(), which allocates and returns the result. libcurl builds + request lines and headers with curlx_dyn_addf(), which formats through the + same allocating code (curl_mvaprintf), so this exercises that path and its + per-byte cost, which differs from the fixed-buffer one. The format is a + request-header-like block: literal text, %s, and a curl_off_t. The + allocation and the free are inside the timed loop on purpose, because a + caller pays them. */ + +static int test_maprintf(int argc, const char **argv) +{ + struct curltime start; + struct curltime end; + timediff_t us; + long long hn; + + curl_off_t loops = 10000000, loop; + + if(argc > 1) { + const char *ptr = argv[1]; + curl_off_t num; + if(!curlx_str_number(&ptr, &num, CURL_OFF_T_MAX) && !*ptr) + loops = num; + } + + start = curlx_now(); + for(loop = 0; loop < loops; loop++) { + char *s = curl_maprintf("%s %s HTTP/1.1\r\n" + "Host: %s\r\n" + "Content-Length: %" CURL_FORMAT_CURL_OFF_T "\r\n", + "POST", "/path/to/resource", "example.com", + (curl_off_t)4096); + if(!s) { + curl_mfprintf(stderr, "curl_maprintf() failed\n"); + return 1; + } + curl_free(s); + } + end = curlx_now(); + us = curlx_timediff_us(end, start); /* how many microseconds */ + hn = loops ? us * 100000 / loops : 0; /* 100 times too big */ + curl_mprintf("Loops: %" CURL_FORMAT_CURL_OFF_T "\n" + "Time: %lld usecs\n" + "Time/loop: %lld.%0lld ns\n", + loops, + (long long)us, + (long long)hn / 100, + (long long)hn % 100); + return 0; +} diff --git a/tests/perf/simpleformat.c b/tests/perf/simpleformat.c new file mode 100644 index 000000000000..ca0051824bc3 --- /dev/null +++ b/tests/perf/simpleformat.c @@ -0,0 +1,70 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ +#include "first.h" + +/* snprintf.c times a format that uses widths and a '*' argument. This test + times a commonly used simple format instead: literal text plus %s, a size_t + and a curl_off_t, with no width or precision. It is the shape of the + messages curl builds with infof() and similar all over the library; it is + modeled on one in lib/imap.c rather than copied from it, so it measures the + common case rather than the elaborate one. The sink is a caller's buffer, + as in snprintf.c. */ + +static int test_simpleformat(int argc, const char **argv) +{ + struct curltime start; + struct curltime end; + timediff_t us; + long long hn; + + curl_off_t loops = 10000000, loop; + + char buffer[256]; + + if(argc > 1) { + const char *ptr = argv[1]; + curl_off_t num; + if(!curlx_str_number(&ptr, &num, CURL_OFF_T_MAX) && !*ptr) + loops = num; + } + + start = curlx_now(); + for(loop = 0; loop < loops; loop++) { + curl_msnprintf(buffer, sizeof(buffer), + "%s: wrote %zu bytes, %" CURL_FORMAT_CURL_OFF_T + " bytes are left for transfer\n", + "upload", (size_t)4096, (curl_off_t)1048576); + } + end = curlx_now(); + us = curlx_timediff_us(end, start); /* how many microseconds */ + hn = loops ? us * 100000 / loops : 0; /* 100 times too big */ + curl_mprintf("Loops: %" CURL_FORMAT_CURL_OFF_T "\n" + "Time: %lld usecs\n" + "Time/loop: %lld.%0lld ns\n", + loops, + (long long)us, + (long long)hn / 100, + (long long)hn % 100); + return 0; +} From 2f1a21b1d9dd078e6a375317fdcf7b3593f2424f Mon Sep 17 00:00:00 2001 From: Tyler Yankee Date: Sun, 6 Sep 2026 09:28:49 -0400 Subject: [PATCH 059/481] build: convert HAS_TIME_T_UNSIGNED to a compile-time check `(time_t)-1 > 0` is a constant expression. Checking this at compile time instead of running a program (a) is faster, and (b) can be checked when cross-compiling. Suggested-by: Florent Castelli Closes #22849 --- CMake/CurlTests.c | 10 ++++++++++ CMakeLists.txt | 11 +---------- configure.ac | 23 +++++++++++------------ 3 files changed, 22 insertions(+), 22 deletions(-) diff --git a/CMake/CurlTests.c b/CMake/CurlTests.c index e8117410fd20..4d8fa1d1d88f 100644 --- a/CMake/CurlTests.c +++ b/CMake/CurlTests.c @@ -388,3 +388,13 @@ int main(void) return 0; } #endif + +#ifdef HAVE_TIME_T_UNSIGNED +#include +static int time_t_is_unsigned[((time_t)-1 > 0) ? 1 : -1]; +int main(void) +{ + (void)time_t_is_unsigned; + return 0; +} +#endif diff --git a/CMakeLists.txt b/CMakeLists.txt index 5fb2e22adb06..4946e6ce32c7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1798,16 +1798,7 @@ if(NOT WIN32 AND NOT CMAKE_CROSSCOMPILING) }" HAVE_WRITABLE_ARGV) endif() -if(NOT CMAKE_CROSSCOMPILING) - include(CheckCSourceRuns) - check_c_source_runs(" - #include - int main(void) - { - time_t t = -1; - return t < 0; - }" HAVE_TIME_T_UNSIGNED) -endif() +curl_internal_test(HAVE_TIME_T_UNSIGNED) curl_internal_test(HAVE_GLIBC_STRERROR_R) curl_internal_test(HAVE_POSIX_STRERROR_R) diff --git a/configure.ac b/configure.ac index 9a382c321e1a..87927f3ec51b 100644 --- a/configure.ac +++ b/configure.ac @@ -4157,22 +4157,21 @@ case $host_os in ;; *) AC_MSG_CHECKING([if time_t is unsigned]) - CURL_RUN_IFELSE( - [ - #include - int main(void) - { - time_t t = -1; - return t < 0; - } - ],[ + AC_COMPILE_IFELSE([ + AC_LANG_SOURCE([[ + #include + static int time_t_is_unsigned[((time_t)-1 > 0) ? 1 : -1]; + int main(void) + { + (void)time_t_is_unsigned; + return 0; + } + ]]) + ],[ AC_MSG_RESULT([yes]) AC_DEFINE(HAVE_TIME_T_UNSIGNED, 1, [Define this if time_t is unsigned]) ],[ AC_MSG_RESULT([no]) - ],[ - dnl cross-compiling, most systems are signed - AC_MSG_RESULT([no]) ]) ;; esac From dd379bef22956fda0a939d89260670328d6cc2f1 Mon Sep 17 00:00:00 2001 From: Daniel Stenberg Date: Mon, 7 Sep 2026 08:51:20 +0200 Subject: [PATCH 060/481] appveyor.yml: fix the yaml, use 'true' instead of 'yes' Follow-up to 5a50313147 Closes #22855 --- appveyor.sh | 2 +- appveyor.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/appveyor.sh b/appveyor.sh index 8fce7859f525..3c2952cfdd04 100644 --- a/appveyor.sh +++ b/appveyor.sh @@ -116,7 +116,7 @@ fi # create artifact -if [[ "${CREATE_ARTIFACT:-}" = 'yes' ]]; then +if [[ "${CREATE_ARTIFACT:-}" = 'true' ]]; then cp /usr/ssl/certs/ca-bundle.crt curl-ca-bundle.crt echo "Checking that https works (it should find curl-ca-bundle.crt if needed)" "${curl}" -v -fsS --retry 6 --retry-all-errors -o /dev/null https://curl.se diff --git a/appveyor.yml b/appveyor.yml index e6231e6a7e8e..ff2414b75109 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -94,7 +94,7 @@ environment: CMAKE_SHA256: dfc2b2afac257555e3b9ce375b12b2883964283a366c17fec96cf4d17e4f1677 CMAKE_GENERATOR: 'Visual Studio 18 2026' CMAKE_GENERATE: '-A x64 -T ClangCl -DBUILD_SHARED_LIBS=OFF -DCURL_USE_OPENSSL=ON -DENABLE_UNICODE=ON' - CREATE_ARTIFACT: yes + CREATE_ARTIFACT: true - job_name: 'CM VS2022, Release, x64, Schannel, Shared, Unicode, Build-tests' APPVEYOR_BUILD_WORKER_IMAGE: 'Visual Studio 2022' From 754bbed04d5c3b6379967c504fe3efd39d0436b7 Mon Sep 17 00:00:00 2001 From: Daniel Stenberg Date: Mon, 7 Sep 2026 10:50:05 +0200 Subject: [PATCH 061/481] VERSIONS: add Rock-solid releases shipping today Closes #22856 --- docs/VERSIONS.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/VERSIONS.md b/docs/VERSIONS.md index c5db67b75881..8a8de04392da 100644 --- a/docs/VERSIONS.md +++ b/docs/VERSIONS.md @@ -356,3 +356,6 @@ version. - Rock-solid curl 8.18.1: February 25, 2026 - Rock-solid curl 8.18.2: September 2, 2026 +- Rock-solid curl 8.14.2: September 7, 2026 +- Rock-solid curl 8.16.1: September 7, 2026 +- Rock-solid curl 8.20.1: September 7, 2026 From 8f6046485e15aa3202277ff9ee5ba64fe02cf2d1 Mon Sep 17 00:00:00 2001 From: Stefan Eissing Date: Mon, 7 Sep 2026 11:44:01 +0200 Subject: [PATCH 062/481] tests: raise some alloc limits Due to the SSH setopts, limits have increased on some tests Closes #22858 --- tests/data/test1 | 2 +- tests/data/test440 | 2 +- tests/data/test767 | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/data/test1 b/tests/data/test1 index a6935ab56a57..dfe31fd479cf 100644 --- a/tests/data/test1 +++ b/tests/data/test1 @@ -48,7 +48,7 @@ Accept: */* -Allocations: 135 +Allocations: 140 Maximum allocated: 136000 diff --git a/tests/data/test440 b/tests/data/test440 index 3ed08f4730dd..53651889bc09 100644 --- a/tests/data/test440 +++ b/tests/data/test440 @@ -75,7 +75,7 @@ https://this.hsts.example./%TESTNUMBER 7 -Allocations: 160 +Allocations: 172 diff --git a/tests/data/test767 b/tests/data/test767 index a0d96f1b6df2..28022381a1bd 100644 --- a/tests/data/test767 +++ b/tests/data/test767 @@ -49,7 +49,7 @@ Accept: */* -Allocations: 135 +Allocations: 141 Maximum allocated: 136000 From f656837720e6fd5aef0162c63e8b75bc073bb065 Mon Sep 17 00:00:00 2001 From: Daniel Stenberg Date: Mon, 7 Sep 2026 15:06:59 +0200 Subject: [PATCH 063/481] ftp: URL encode file names when wildcarding Verify in test 2805 Reported-by: Stanislav Fort Closes #22860 --- lib/ftp.c | 7 +++- tests/data/Makefile.am | 1 + tests/data/test2805 | 60 +++++++++++++++++++++++++++++++++++ tests/directories.pm | 13 ++++++++ tests/ftpserver.pl | 1 + tests/libtest/Makefile.inc | 1 + tests/libtest/lib2805.c | 65 ++++++++++++++++++++++++++++++++++++++ 7 files changed, 147 insertions(+), 1 deletion(-) create mode 100644 tests/data/test2805 create mode 100644 tests/libtest/lib2805.c diff --git a/lib/ftp.c b/lib/ftp.c index ccdaf691c2f8..6a952a82690d 100644 --- a/lib/ftp.c +++ b/lib/ftp.c @@ -4083,8 +4083,13 @@ static CURLcode wc_statemach(struct Curl_easy *data, /* filelist has at least one file, lets get first one */ struct Curl_llist_node *head = Curl_llist_head(&wildcard->filelist); struct curl_fileinfo *finfo = Curl_node_elem(head); + char *tmp_path; + char *enc = curl_easy_escape(NULL, finfo->filename, 0); + if(!enc) + return CURLE_OUT_OF_MEMORY; - char *tmp_path = curl_maprintf("%s%s", wildcard->path, finfo->filename); + tmp_path = curl_maprintf("%s%s", wildcard->path, enc); + curl_free(enc); if(!tmp_path) return CURLE_OUT_OF_MEMORY; diff --git a/tests/data/Makefile.am b/tests/data/Makefile.am index 674d88007979..6ada79e84bb3 100644 --- a/tests/data/Makefile.am +++ b/tests/data/Makefile.am @@ -2009,6 +2009,7 @@ TESTCASES = \ test2721 \ test2722 \ test2723 \ + test2805 \ test2885 \ test3000 \ test3001 \ diff --git a/tests/data/test2805 b/tests/data/test2805 new file mode 100644 index 000000000000..2006e4054953 --- /dev/null +++ b/tests/data/test2805 @@ -0,0 +1,60 @@ + + + + +FTP +LIST +wildcardmatch +ftplistparser + + + +# Directory content is generated by the ftp server itself + +# Client-side + + +ftp + + +lib%TESTNUMBER + + +FTP wildcards with percent sign in file name + + +ftp://%HOSTIP:%FTPPORT/fully_simulated/percen%74/*.txt + + + +############################################ +# Verify data after the test has been "shot" + + +0 + + + +USER anonymous +PASS ftp@example.com +PWD +CWD fully_simulated +CWD percent +EPSV +TYPE A +LIST +EPSV +TYPE I +RETR someothertext.txt +EPSV +RETR some%2etxt +QUIT + + +match: 'someothertext.txt' against pattern '*.txt' +match: 'some%2etxt' against pattern '*.txt' +Some junk ;-) This file does not really exist. +percent in file name. + + + diff --git a/tests/directories.pm b/tests/directories.pm index cbb5921cd396..f868ead9cb34 100644 --- a/tests/directories.pm +++ b/tests/directories.pm @@ -179,7 +179,20 @@ my %file_someothertext_txt = ( 'perm' => "rw-r--r--" ); +my %file_percent_txt = ( + 'name' => "some%2etxt", + 'content' => "percent in file name.\n", + 'time' => "Apr 17 11:01", + 'dostime' => "04-17-10 11:01AM", + 'perm' => "rw-r--r--" +); + my %lists = ( + '/fully_simulated/percent/' => { + 'files' => [ \%file_someothertext_txt, \%file_percent_txt], + 'eol' => "\r\n", + 'type' => "unix" + }, '/fully_simulated/' => { 'files' => [ \%dir_dot, \%dir_ddot, \%dir_DOS, \%dir_UNIX ], 'eol' => "\r\n", diff --git a/tests/ftpserver.pl b/tests/ftpserver.pl index 23278e6de665..80bb606be1c9 100755 --- a/tests/ftpserver.pl +++ b/tests/ftpserver.pl @@ -2115,6 +2115,7 @@ sub LIST_ftp { my @ftpdir = ftp_contentlist($ftptargetdir); # old hard-coded style for(@ftpdir) { + logmsg "LIST: $_"; senddata $_; } } diff --git a/tests/libtest/Makefile.inc b/tests/libtest/Makefile.inc index 3bd7b5f6962f..53439cab35f0 100644 --- a/tests/libtest/Makefile.inc +++ b/tests/libtest/Makefile.inc @@ -313,6 +313,7 @@ TESTS_C = \ lib2505.c \ lib2506.c \ lib2700.c \ + lib2805.c \ lib3010.c \ lib3025.c \ lib3026.c \ diff --git a/tests/libtest/lib2805.c b/tests/libtest/lib2805.c new file mode 100644 index 000000000000..7e2c8bb38a0b --- /dev/null +++ b/tests/libtest/lib2805.c @@ -0,0 +1,65 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ +#include "first.h" + +static int fnmatch2805(void *ptr, const char *pattern, const char *string) +{ + (void)ptr; + curl_mprintf("match: '%s' against pattern '%s'\n", string, pattern); + return CURL_FNMATCHFUNC_MATCH; +} + +static CURLcode test_lib2805(const char *URL) +{ + CURLcode result; + CURL *curl; + + if(curl_global_init(CURL_GLOBAL_ALL) != CURLE_OK) { + curl_mfprintf(stderr, "curl_global_init() failed\n"); + return TEST_ERR_MAJOR_BAD; + } + + curl = curl_easy_init(); + if(!curl) { + curl_mfprintf(stderr, "curl_easy_init() failed\n"); + curl_global_cleanup(); + return TEST_ERR_MAJOR_BAD; + } + + easy_setopt(curl, CURLOPT_URL, URL); + easy_setopt(curl, CURLOPT_WILDCARDMATCH, 1L); + easy_setopt(curl, CURLOPT_FNMATCH_FUNCTION, fnmatch2805); + easy_setopt(curl, CURLOPT_TIMEOUT_MS, (long)TEST_HANG_TIMEOUT); + + result = curl_easy_perform(curl); + if(result) { + curl_mfprintf(stderr, "curl_easy_perform() failed %d\n", (int)result); + goto test_cleanup; + } + +test_cleanup: + curl_easy_cleanup(curl); + curl_global_cleanup(); + return result; +} From 04692d3bae9bc0ef91fd2f78ac80828692c0b5c1 Mon Sep 17 00:00:00 2001 From: Stefan Eissing Date: Mon, 7 Sep 2026 17:13:43 +0200 Subject: [PATCH 064/481] cshutdown: make it smaller Remove the members `multi *` and `initialized`, passing multi or admin handles as parameters. When adding, pass a "max_keep" number to cshutdown for limiting growth. conncache calculates that based on multi settings and current pool size. Closes #22862 --- lib/conncache.c | 40 ++++++++++++++--------- lib/cshutdn.c | 84 ++++++++++++++++++++----------------------------- lib/cshutdn.h | 18 +++++------ lib/multi.c | 14 ++++----- 4 files changed, 74 insertions(+), 82 deletions(-) diff --git a/lib/conncache.c b/lib/conncache.c index d4ae52f9f3c1..5de4b75a1dbb 100644 --- a/lib/conncache.c +++ b/lib/conncache.c @@ -182,7 +182,6 @@ static void cpool_discard_conn(struct cpool *cpool, struct connectdata *conn, bool aborted) { - struct cshutdn *cshutdn; struct Curl_easy *admin; bool done = FALSE; @@ -221,11 +220,19 @@ static void cpool_discard_conn(struct cpool *cpool, Curl_conn_shutdown_once(admin, conn, &done); } - cshutdn = Curl_cshutdn_get(data); - if(done || !cshutdn) + if(done || !data->multi) Curl_conn_terminate(admin, conn, FALSE); - else - Curl_cshutdn_add(cshutdn, conn, cpool->num_conn); + else { + struct Curl_multi *multi = data->multi; + size_t max_shutdowns = multi->max_total_connections; + if(cpool->num_conn < max_shutdowns) + max_shutdowns -= cpool->num_conn; + else if(max_shutdowns) + max_shutdowns = 1; + else /* no connection limit set, let's restrict growth nevertheless */ + max_shutdowns = CURLMAX(cpool->num_conn / 4, 128); + Curl_cshutdn_add(&multi->cshutdn, multi, conn, max_shutdowns); + } } void Curl_cpool_destroy(struct cpool *cpool, struct Curl_easy *admin) @@ -454,7 +461,7 @@ int Curl_cpool_check_limits(struct Curl_easy *data, const struct curltime *pnow) { struct cpool *cpool = cpool_get_instance(data); - struct cshutdn *cshutdn = Curl_cshutdn_get(data); + struct Curl_multi *multi = data->multi; struct Curl_easy *admin; struct cpool_bundle *bundle; size_t dest_limit = 0; @@ -466,9 +473,9 @@ int Curl_cpool_check_limits(struct Curl_easy *data, return CPOOL_LIMIT_OK; /* multi determines the limits, no matter who owns the pool */ - if(data->multi) { - dest_limit = data->multi->max_host_connections; - total_limit = data->multi->max_total_connections; + if(multi) { + dest_limit = multi->max_host_connections; + total_limit = multi->max_total_connections; } if(!dest_limit && !total_limit) @@ -481,11 +488,13 @@ int Curl_cpool_check_limits(struct Curl_easy *data, bundle = cpool_find_bundle(cpool, conn); live = bundle ? Curl_llist_count(&bundle->conns) : 0; - shutdowns = Curl_cshutdn_dest_count(cshutdn, conn->destination); + shutdowns = + multi ? Curl_cshutdn_dest_count(&multi->cshutdn, conn->destination) : 0; while((live + shutdowns) >= dest_limit) { if(shutdowns) { /* close one connection in shutdown right away, if we can */ - if(!Curl_cshutdn_close_oldest(cshutdn, conn->destination)) + if(!Curl_cshutdn_close_oldest(&multi->cshutdn, multi->admin, + conn->destination)) break; } else if(!bundle) @@ -508,7 +517,8 @@ int Curl_cpool_check_limits(struct Curl_easy *data, bundle = cpool_find_bundle(cpool, conn); live = bundle ? Curl_llist_count(&bundle->conns) : 0; } - shutdowns = Curl_cshutdn_dest_count(cshutdn, conn->destination); + shutdowns = multi ? + Curl_cshutdn_dest_count(&multi->cshutdn, conn->destination) : 0; } if((live + shutdowns) >= dest_limit) { res = CPOOL_LIMIT_DEST; @@ -517,11 +527,11 @@ int Curl_cpool_check_limits(struct Curl_easy *data, } if(total_limit) { - shutdowns = Curl_cshutdn_count(cshutdn); + shutdowns = multi ? Curl_cshutdn_count(&multi->cshutdn) : 0; while((cpool->num_conn + shutdowns) >= total_limit) { if(shutdowns) { /* close one connection in shutdown right away, if we can */ - if(!Curl_cshutdn_close_oldest(cshutdn, NULL)) + if(!Curl_cshutdn_close_oldest(&multi->cshutdn, multi->admin, NULL)) break; } else { @@ -536,7 +546,7 @@ int Curl_cpool_check_limits(struct Curl_easy *data, oldest_idle->connection_id, cpool->num_conn, total_limit); cpool_evict_conn(cpool, admin, oldest_idle); } - shutdowns = Curl_cshutdn_count(cshutdn); + shutdowns = multi ? Curl_cshutdn_count(&multi->cshutdn) : 0; } if((cpool->num_conn + shutdowns) >= total_limit) { res = CPOOL_LIMIT_TOTAL; diff --git a/lib/cshutdn.c b/lib/cshutdn.c index e9ce1f6e7b8a..9fae2fab6812 100644 --- a/lib/cshutdn.c +++ b/lib/cshutdn.c @@ -38,13 +38,6 @@ #include "curlx/strparse.h" -struct cshutdn *Curl_cshutdn_get(struct Curl_easy *data) -{ - if(data && data->multi) - return &data->multi->cshutdn; - return NULL; -} - static void cshutdn_run_conn_handler(struct Curl_easy *data, struct connectdata *conn) { @@ -166,6 +159,7 @@ void Curl_conn_terminate(struct Curl_easy *admin, } static bool cshutdn_destroy_oldest(struct cshutdn *cshutdn, + struct Curl_easy *admin, const char *destination) { struct Curl_llist_node *e; @@ -184,8 +178,8 @@ static bool cshutdn_destroy_oldest(struct cshutdn *cshutdn, conn = Curl_node_elem(e); Curl_node_remove(e); sigpipe_init(&sigpipe_ctx); - sigpipe_apply(cshutdn->multi->admin, &sigpipe_ctx); - Curl_conn_terminate(cshutdn->multi->admin, conn, FALSE); + sigpipe_apply(admin, &sigpipe_ctx); + Curl_conn_terminate(admin, conn, FALSE); sigpipe_restore(&sigpipe_ctx); return TRUE; } @@ -193,10 +187,11 @@ static bool cshutdn_destroy_oldest(struct cshutdn *cshutdn, } bool Curl_cshutdn_close_oldest(struct cshutdn *cshutdn, + struct Curl_easy *admin, const char *destination) { if(cshutdn) { - return cshutdn_destroy_oldest(cshutdn, destination); + return cshutdn_destroy_oldest(cshutdn, admin, destination); } return FALSE; } @@ -204,6 +199,7 @@ bool Curl_cshutdn_close_oldest(struct cshutdn *cshutdn, #define NUM_POLLS_ON_STACK 10 static CURLcode cshutdn_wait(struct cshutdn *cshutdn, + struct Curl_easy *admin, int timeout_ms) { struct pollfd a_few_on_stack[NUM_POLLS_ON_STACK]; @@ -212,7 +208,7 @@ static CURLcode cshutdn_wait(struct cshutdn *cshutdn, Curl_pollfds_init(&cpfds, a_few_on_stack, NUM_POLLS_ON_STACK); - result = Curl_cshutdn_add_pollfds(cshutdn, &cpfds); + result = Curl_cshutdn_add_pollfds(cshutdn, admin, &cpfds); if(result) goto out; @@ -224,11 +220,11 @@ static CURLcode cshutdn_wait(struct cshutdn *cshutdn, } static void cshutdn_perform(struct cshutdn *cshutdn, + struct Curl_easy *admin, struct Curl_sigpipe_ctx *sigpipe_ctx) { struct Curl_llist_node *e = Curl_llist_head(&cshutdn->list); struct Curl_llist_node *enext; - struct Curl_easy *admin = cshutdn->multi->admin; struct connectdata *conn; timediff_t next_expire_ms = 0, ms; bool done; @@ -262,9 +258,9 @@ static void cshutdn_perform(struct cshutdn *cshutdn, } static void cshutdn_terminate_all(struct cshutdn *cshutdn, + struct Curl_easy *admin, int timeout_ms) { - struct Curl_easy *admin = cshutdn->multi->admin; struct curltime started = *Curl_pgrs_now(admin); struct Curl_llist_node *e; struct Curl_sigpipe_ctx sigpipe_ctx; @@ -276,7 +272,7 @@ static void cshutdn_terminate_all(struct cshutdn *cshutdn, timediff_t spent_ms; int remain_ms; - cshutdn_perform(cshutdn, &sigpipe_ctx); + cshutdn_perform(cshutdn, admin, &sigpipe_ctx); if(!Curl_llist_head(&cshutdn->list)) { CURL_TRC_M(admin, "[SHUTDOWN] shutdown finished cleanly"); @@ -292,7 +288,7 @@ static void cshutdn_terminate_all(struct cshutdn *cshutdn, } remain_ms = timeout_ms - (int)spent_ms; - if(cshutdn_wait(cshutdn, remain_ms)) { + if(cshutdn_wait(cshutdn, admin, remain_ms)) { CURL_TRC_M(admin, "[SHUTDOWN] shutdown finished, aborted"); break; } @@ -311,24 +307,18 @@ static void cshutdn_terminate_all(struct cshutdn *cshutdn, sigpipe_restore(&sigpipe_ctx); } -int Curl_cshutdn_init(struct cshutdn *cshutdn, - struct Curl_multi *multi) +void Curl_cshutdn_init(struct cshutdn *cshutdn) { - DEBUGASSERT(multi); - cshutdn->multi = multi; Curl_llist_init(&cshutdn->list, NULL); - cshutdn->initialized = TRUE; - return 0; /* good */ } void Curl_cshutdn_destroy(struct cshutdn *cshutdn, struct Curl_easy *admin) { - if(cshutdn->initialized && admin) { + if(admin) { int timeout_ms = 0; /* for testing, run graceful shutdown */ #ifdef DEBUGBUILD - DEBUGASSERT(!admin->mid); { const char *p = getenv("CURL_GRACEFUL_SHUTDOWN"); if(p) { @@ -338,13 +328,12 @@ void Curl_cshutdn_destroy(struct cshutdn *cshutdn, } } #endif - - CURL_TRC_M(admin, "[SHUTDOWN] destroy, %zu connections, timeout=%dms", - Curl_llist_count(&cshutdn->list), timeout_ms); - cshutdn_terminate_all(cshutdn, timeout_ms); + if(Curl_llist_count(&cshutdn->list)) { + CURL_TRC_M(admin, "[SHUTDOWN] destroy, %zu connections, timeout=%dms", + Curl_llist_count(&cshutdn->list), timeout_ms); + cshutdn_terminate_all(cshutdn, admin, timeout_ms); + } } - cshutdn->multi = NULL; - cshutdn->initialized = FALSE; } size_t Curl_cshutdn_count(struct cshutdn *cshutdn) @@ -372,38 +361,32 @@ size_t Curl_cshutdn_dest_count(struct cshutdn *cshutdn, return 0; } -static CURLMcode cshutdn_update_ev(struct cshutdn *cshutdn, +static CURLMcode cshutdn_update_ev(struct Curl_multi *multi, struct connectdata *conn) { - struct Curl_easy *admin = cshutdn->multi->admin; CURLMcode mresult; - - DEBUGASSERT(cshutdn); - DEBUGASSERT(cshutdn->multi->socket_cb); - - Curl_attach_connection(admin, conn, FALSE); - mresult = Curl_multi_ev_assess_conn(cshutdn->multi, admin, conn); - Curl_detach_connection(admin); + Curl_attach_connection(multi->admin, conn, FALSE); + mresult = Curl_multi_ev_assess_conn(multi, multi->admin, conn); + Curl_detach_connection(multi->admin); return mresult; } void Curl_cshutdn_add(struct cshutdn *cshutdn, + struct Curl_multi *multi, struct connectdata *conn, - size_t conns_in_pool) + size_t max_shutdowns) { - struct Curl_easy *admin = cshutdn->multi->admin; - size_t max_total = cshutdn->multi->max_total_connections; + struct Curl_easy *admin = multi->admin; /* Add the connection to our shutdown list for non-blocking shutdown * during multi processing. */ - if(max_total > 0 && - (max_total <= (conns_in_pool + Curl_llist_count(&cshutdn->list)))) { + if(max_shutdowns <= Curl_llist_count(&cshutdn->list)) { CURL_TRC_M(admin, "[SHUTDOWN] discarding oldest shutdown connection " - "due to connection limit of %zu", max_total); - cshutdn_destroy_oldest(cshutdn, NULL); + "due to shutdown limit of %zu", max_shutdowns); + cshutdn_destroy_oldest(cshutdn, admin, NULL); } - if(cshutdn->multi->socket_cb && cshutdn_update_ev(cshutdn, conn)) { + if(multi->socket_cb && cshutdn_update_ev(multi, conn)) { CURL_TRC_M(admin, "[SHUTDOWN] update events failed, discarding #%" FMT_OFF_T, conn->connection_id); Curl_conn_terminate(admin, conn, FALSE); @@ -417,18 +400,19 @@ void Curl_cshutdn_add(struct cshutdn *cshutdn, } void Curl_cshutdn_perform(struct cshutdn *cshutdn, + struct Curl_easy *admin, struct Curl_sigpipe_ctx *sigpipe_ctx) { - cshutdn_perform(cshutdn, sigpipe_ctx); + cshutdn_perform(cshutdn, admin, sigpipe_ctx); } /* return fd_set info about the shutdown connections */ void Curl_cshutdn_setfds(struct cshutdn *cshutdn, + struct Curl_easy *admin, fd_set *read_fd_set, fd_set *write_fd_set, int *maxfd) { if(Curl_llist_head(&cshutdn->list)) { - struct Curl_easy *admin = cshutdn->multi->admin; struct Curl_llist_node *e; struct easy_pollset ps; @@ -465,13 +449,13 @@ void Curl_cshutdn_setfds(struct cshutdn *cshutdn, /* return information about the shutdown connections */ unsigned int Curl_cshutdn_add_waitfds(struct cshutdn *cshutdn, + struct Curl_easy *admin, struct Curl_waitfds *cwfds) { unsigned int need = 0; if(Curl_llist_head(&cshutdn->list)) { struct Curl_llist_node *e; - struct Curl_easy *admin = cshutdn->multi->admin; struct easy_pollset ps; struct connectdata *conn; CURLcode result; @@ -493,6 +477,7 @@ unsigned int Curl_cshutdn_add_waitfds(struct cshutdn *cshutdn, } CURLcode Curl_cshutdn_add_pollfds(struct cshutdn *cshutdn, + struct Curl_easy *admin, struct curl_pollfds *cpfds) { CURLcode result = CURLE_OK; @@ -500,7 +485,6 @@ CURLcode Curl_cshutdn_add_pollfds(struct cshutdn *cshutdn, if(Curl_llist_head(&cshutdn->list)) { struct Curl_llist_node *e; struct easy_pollset ps; - struct Curl_easy *admin = cshutdn->multi->admin; struct connectdata *conn; Curl_pollset_init(&ps); diff --git a/lib/cshutdn.h b/lib/cshutdn.h index a0338f64882f..b0da4f49b220 100644 --- a/lib/cshutdn.h +++ b/lib/cshutdn.h @@ -53,16 +53,10 @@ void Curl_conn_terminate(struct Curl_easy *admin, * sockets to monitor via the multi handle. */ struct cshutdn { struct Curl_llist list; /* connections being shut down */ - struct Curl_multi *multi; /* the multi owning this */ - BIT(initialized); }; -/* Get the cshutdn instance relevant for `data` or NULL if there is none */ -struct cshutdn *Curl_cshutdn_get(struct Curl_easy *data); - /* Init as part of the given multi handle. */ -int Curl_cshutdn_init(struct cshutdn *cshutdn, - struct Curl_multi *multi); +void Curl_cshutdn_init(struct cshutdn *cshutdn); /* Terminate all remaining connections and free resources. */ void Curl_cshutdn_destroy(struct cshutdn *cshutdn, @@ -79,27 +73,33 @@ size_t Curl_cshutdn_dest_count(struct cshutdn *cshutdn, * when destination is NULL for any destination. * Return TRUE if a connection has been closed. */ bool Curl_cshutdn_close_oldest(struct cshutdn *cshutdn, + struct Curl_easy *admin, const char *destination); /* Add a connection to have it shut down. Terminate the oldest - * connection when total connection limit of multi is being reached. */ + * connection when shutdowns exceed max_shutdowns. */ void Curl_cshutdn_add(struct cshutdn *cshutdn, + struct Curl_multi *multi, struct connectdata *conn, - size_t conns_in_pool); + size_t max_shutdowns); /* Add sockets and POLLIN/OUT flags for connections being shut down. */ CURLcode Curl_cshutdn_add_pollfds(struct cshutdn *cshutdn, + struct Curl_easy *admin, struct curl_pollfds *cpfds); unsigned int Curl_cshutdn_add_waitfds(struct cshutdn *cshutdn, + struct Curl_easy *admin, struct Curl_waitfds *cwfds); void Curl_cshutdn_setfds(struct cshutdn *cshutdn, + struct Curl_easy *admin, fd_set *read_fd_set, fd_set *write_fd_set, int *maxfd); /* Run maintenance on all connections. */ void Curl_cshutdn_perform(struct cshutdn *cshutdn, + struct Curl_easy *admin, struct Curl_sigpipe_ctx *sigpipe_ctx); #endif /* HEADER_CURL_CSHUTDN_H */ diff --git a/lib/multi.c b/lib/multi.c index 9307401d8181..d51e68b7803d 100644 --- a/lib/multi.c +++ b/lib/multi.c @@ -260,6 +260,7 @@ struct Curl_multi *Curl_multi_handle(uint32_t xfer_table_size, Curl_uint32_bset_init(&multi->pending); Curl_uint32_bset_init(&multi->msgsent); Curl_hash_init(&multi->proto_hash, 23, CURL_HASH_TYPE_BYTES, ph_freeentry); + Curl_cshutdn_init(&multi->cshutdn); multi->multiplexing = TRUE; multi->max_concurrent_streams = 100; @@ -286,9 +287,6 @@ struct Curl_multi *Curl_multi_handle(uint32_t xfer_table_size, Curl_uint32_tbl_add(&multi->xfers, multi->admin, &multi->admin->mid); Curl_uint32_bset_add(&multi->process, multi->admin->mid); - if(Curl_cshutdn_init(&multi->cshutdn, multi)) - goto error; - Curl_cpool_init(&multi->cpool, NULL, chashsize); #ifdef USE_SSL @@ -1279,8 +1277,8 @@ CURLMcode curl_multi_fdset(CURLM *m, } while(Curl_uint32_bset_next(&multi->process, mid, &mid)); } - Curl_cshutdn_setfds(&multi->cshutdn, read_fd_set, write_fd_set, - &this_max_fd); + Curl_cshutdn_setfds(&multi->cshutdn, multi->admin, + read_fd_set, write_fd_set, &this_max_fd); *max_fd = this_max_fd; Curl_pollset_cleanup(&ps); @@ -1328,7 +1326,7 @@ CURLMcode curl_multi_waitfds(CURLM *m, } while(Curl_uint32_bset_next(&multi->process, mid, &mid)); } - need += Curl_cshutdn_add_waitfds(&multi->cshutdn, &cwfds); + need += Curl_cshutdn_add_waitfds(&multi->cshutdn, multi->admin, &cwfds); if(need != cwfds.n && ufds) mresult = CURLM_OUT_OF_MEMORY; @@ -1563,7 +1561,7 @@ static CURLMcode multi_wait(struct Curl_multi *multi, } while(Curl_uint32_bset_next(&multi->process, mid, &mid)); } - if(Curl_cshutdn_add_pollfds(&multi->cshutdn, &cpfds)) { + if(Curl_cshutdn_add_pollfds(&multi->cshutdn, multi->admin, &cpfds)) { mresult = CURLM_OUT_OF_MEMORY; goto out; } @@ -2741,7 +2739,7 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, #ifdef USE_RESOLV_THREADED Curl_async_thrdd_multi_process(multi); #endif - Curl_cshutdn_perform(&multi->cshutdn, sigpipe_ctx); + Curl_cshutdn_perform(&multi->cshutdn, multi->admin, sigpipe_ctx); goto out; } From 7f11ae14ef57810018fc934f3cb6ce8db6704559 Mon Sep 17 00:00:00 2001 From: Viktor Szakats Date: Mon, 7 Sep 2026 22:04:49 +0200 Subject: [PATCH 065/481] GHA/curl-for-win: upload artifact as `curl.exe` (without using zip) To make it easier to run local tests. Example: https://github.com/curl/curl/actions/runs/34158040472?pr=22863 https://github.com/curl/curl/actions/runs/34158040472/artifacts/10031671842 Ref: https://github.blog/changelog/2026-02-26-github-actions-now-supports-uploading-and-downloading-non-zipped-artifacts/ Closes #22863 --- .github/workflows/curl-for-win.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/curl-for-win.yml b/.github/workflows/curl-for-win.yml index b84241f3e426..39f6d3344de1 100644 --- a/.github/workflows/curl-for-win.yml +++ b/.github/workflows/curl-for-win.yml @@ -181,6 +181,7 @@ jobs: name: 'curl-windows-snapshot-tool' retention-days: 5 path: curl-*-*-*/curl* + archive: false win-gcc-zlibold-x64: name: 'Windows gcc zlib-classic (x64)' From 81787b0b34c496c46870751f6ca438346036d7a0 Mon Sep 17 00:00:00 2001 From: Viktor Szakats Date: Mon, 7 Sep 2026 22:58:36 +0200 Subject: [PATCH 066/481] test1679: unset `SSL_CERT_FILE`, use `TESTNUMBER` To fix a `SSL_CERT_FILE` env breaking the test. For example the `ubuntu-24.04-firewall` runner image is setting this env. Follow-up to 19d0f4acfa9df373e8e1f987c11b7165e1edac34 #22745 Cherry-picked from #22864 Closes #22866 --- tests/data/test1679 | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/data/test1679 b/tests/data/test1679 index 97582d52f973..618d28ae787b 100644 --- a/tests/data/test1679 +++ b/tests/data/test1679 @@ -30,6 +30,9 @@ http --libcurl resets SSL verification after --next + +SSL_CERT_FILE + http://%HOSTIP:%HTTPPORT/we/want/%TESTNUMBER --libcurl %LOGDIR/test%TESTNUMBER.c --insecure --next http://%HOSTIP:%HTTPPORT/we/want/%TESTNUMBER @@ -54,7 +57,7 @@ $_ = '' if /CURLOPT_INTERLEAVEDATA/ $_ = '' if /CURLOPT_SSH_KNOWNHOSTS/ -%includetext %SRCDIR/data/data1679.c% +%includetext %SRCDIR/data/data%TESTNUMBER.c% From 12cfc836c6fe8f4e4e5001f400d677f0aa530421 Mon Sep 17 00:00:00 2001 From: Bobbeh Rhino Date: Mon, 7 Sep 2026 20:36:39 -0400 Subject: [PATCH 067/481] imap: compare selected mailbox names case-sensitively Closes #22868 --- lib/imap.c | 4 +++- tests/data/test804 | 11 ++++++----- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/lib/imap.c b/lib/imap.c index 9586e4f6d6eb..c9037e6662f8 100644 --- a/lib/imap.c +++ b/lib/imap.c @@ -2071,7 +2071,9 @@ static CURLcode imap_perform(struct Curl_easy *data, bool *connected, /* Determine if the requested mailbox (with the same UIDVALIDITY if set) has already been selected on this connection */ if(imap->mailbox && imapc->mailbox && - curl_strequal(imap->mailbox, imapc->mailbox) && + (!strcmp(imap->mailbox, imapc->mailbox) || + (curl_strequal(imap->mailbox, "INBOX") && + curl_strequal(imapc->mailbox, "INBOX"))) && (!imap->uidvalidity_set || !imapc->mb_uidvalidity_set || (imap->uidvalidity == imapc->mb_uidvalidity))) selected = TRUE; diff --git a/tests/data/test804 b/tests/data/test804 index d60f0e0d0fd2..ea31b524b4bf 100644 --- a/tests/data/test804 +++ b/tests/data/test804 @@ -24,10 +24,10 @@ body imap -IMAP does not perform SELECT if reusing the same mailbox +IMAP selects a mailbox when its case changes -'imap://%HOSTIP:%IMAPPORT/%TESTNUMBER/;MAILINDEX=123/;SECTION=1' 'imap://%HOSTIP:%IMAPPORT/%TESTNUMBER/;MAILINDEX=456/;SECTION=2.3' -u user:secret +'imap://%HOSTIP:%IMAPPORT/Private/;MAILINDEX=123/;SECTION=1' 'imap://%HOSTIP:%IMAPPORT/private/;MAILINDEX=456/;SECTION=2.3' -u user:secret @@ -36,10 +36,11 @@ IMAP does not perform SELECT if reusing the same mailbox A001 CAPABILITY A002 LOGIN user secret -A003 SELECT %TESTNUMBER +A003 SELECT Private A004 FETCH 123 BODY[1] -A005 FETCH 456 BODY[2.3] -A006 LOGOUT +A005 SELECT private +A006 FETCH 456 BODY[2.3] +A007 LOGOUT From a1614c3bcf1593eb524b37792124357d97a5429c Mon Sep 17 00:00:00 2001 From: Daniel Stenberg Date: Tue, 8 Sep 2026 09:07:13 +0200 Subject: [PATCH 068/481] hsts: on load, only remove exact duplicates Added test3109: verify HSTS when parent+subdomain expires before child Reported-by: Stanislav Fort Closes #22869 --- lib/hsts.c | 2 +- tests/data/Makefile.am | 2 +- tests/data/test3109 | 86 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 88 insertions(+), 2 deletions(-) create mode 100644 tests/data/test3109 diff --git a/lib/hsts.c b/lib/hsts.c index 73c01db01892..ef4128fd64d1 100644 --- a/lib/hsts.c +++ b/lib/hsts.c @@ -463,7 +463,7 @@ static CURLcode hsts_add_host_expire(struct hsts *h, if(hostlen) { /* only add it if not already present */ - e = hsts_check(h, host, hostlen, subdomain); + e = hsts_check(h, host, hostlen, FALSE); if(!e) result = hsts_create(h, host, hostlen, subdomain, expires); /* 'host' is not necessarily null-terminated */ diff --git a/tests/data/Makefile.am b/tests/data/Makefile.am index 6ada79e84bb3..54e675fa28c8 100644 --- a/tests/data/Makefile.am +++ b/tests/data/Makefile.am @@ -2056,7 +2056,7 @@ TESTCASES = \ test3104 \ test3105 \ test3106 \ - \ + test3109 \ test3200 \ test3201 \ test3202 \ diff --git a/tests/data/test3109 b/tests/data/test3109 new file mode 100644 index 000000000000..d256a4041652 --- /dev/null +++ b/tests/data/test3109 @@ -0,0 +1,86 @@ + + + + +HTTP +HTTP proxy +HSTS + + + + + +# we use this as response to a CONNECT + +HTTP/1.1 200 OK +Server: fake + + + + +HTTP/1.1 200 OK +Date: Tue, 09 Nov 2010 14:49:00 GMT +Content-Type: text/html +Content-Length: 0 + + + + + + +http +http-proxy +https + + +HSTS +proxy +https +Debug + + +# October 1, 2024 + +CURL_HSTS_HTTP=yes +CURL_TIME=1727733600 + + +# the child name has a later expiury than the parent so it must not be removed +# before + +.example.test "20241010 09:42:27" +.child.example.test "20251010 09:42:27" + + + +HSTS parent expires before child + + +-x http://%HOSTIP:%PROXYPORT http://child.example.test:%HTTPSPORT/%TESTNUMBER --hsts %LOGDIR/input%TESTNUMBER --insecure + + +test-duphandle + + + + + + +HTTP/1.1 200 OK%CR +Server: fake%CR +%CR +HTTP/1.1 200 OK +Date: Tue, 09 Nov 2010 14:49:00 GMT +Content-Type: text/html +Content-Length: 0 + + + + +# Your HSTS cache. https://curl.se/docs/hsts.html +# This file was generated by libcurl! Edit at your own risk. +.example.test "20241010 09:42:27" +.child.example.test "20251010 09:42:27" + + + From e5e1c356107ea4f8208757deb69776d94496cfae Mon Sep 17 00:00:00 2001 From: Viktor Szakats Date: Mon, 7 Sep 2026 22:46:58 +0200 Subject: [PATCH 069/481] GHA/linux: enable egress firewall reporting in one job, disable MotD In the 'CM awslc' job. It's a preview feature and needs downgrading to Ubuntu 24.04. It uses a MitM proxy to record network access and causes a 15-20% runtime increase according to documentation. Notably it breaks pytests 50 and 51 (SCP/SFTP): https://github.com/curl/curl/actions/runs/34165541078/job/101875730179?pr=22864 The observed performance impact is higher than documented, at 1m37s -> 2m6s. apt install steps takes 2x time, configure/build: +20-40%, running tests: +10%. This makes this practical only for security critical jobs, e.g. creating build artifacts or other lasting or external impact (once URL filtering is supported/documented). Also useful for occasional review and debugging. Before: https://github.com/curl/curl/actions/runs/34192638355/job/101953659576 After: https://github.com/curl/curl/actions/runs/34201459582/job/101981056237?pr=22864 Also: - GHA/checksrc: add actionlint exception for the firewalled runner image. - GHA/linux: disable MotD (message of the day) Ubuntu feature, discovered via the firewall log, accessing `https://motd.ubuntu.com/aptnews.json` twice, during package install. Ref: https://github.com/github-early-access/actions-native-egress-firewall Closes #22864 --- .github/workflows/checksrc.yml | 2 +- .github/workflows/linux.yml | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/checksrc.yml b/.github/workflows/checksrc.yml index 2d8bb26a3541..323a68369c2b 100644 --- a/.github/workflows/checksrc.yml +++ b/.github/workflows/checksrc.yml @@ -212,7 +212,7 @@ jobs: eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)" export SHELLCHECK_OPTS='--exclude=1090,1091,2086,2153 --enable=avoid-nullary-conditions,deprecate-which' actionlint --version - actionlint --ignore matrix --ignore ubuntu-26.04 .github/workflows/*.yml + actionlint --ignore matrix --ignore ubuntu-26.04 --ignore ubuntu-24.04-firewall .github/workflows/*.yml - name: 'shellcheck CI' run: | diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index d854858af5dc..754011fce04f 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -81,6 +81,7 @@ jobs: configure: --with-openssl=/home/runner/awslc --enable-ech --enable-ntlm --enable-smb - name: 'awslc' + image: ubuntu-24.04-firewall install_packages: libidn2-dev install_steps: awslc clean generate: -DOPENSSL_ROOT_DIR=/home/runner/awslc -DUSE_ECH=ON -DCMAKE_UNITY_BUILD=OFF -DCURL_DROP_UNUSED=ON -DCURL_PATCHSTAMP=test-patch -DCURL_ENABLE_NTLM=ON @@ -474,6 +475,7 @@ jobs: ${{ contains(matrix.build.install_steps, 'pytest') && 'apache2 apache2-dev libnghttp2-dev vsftpd dante-server' || '' }} run: | + sudo rm -f /etc/apt/apt.conf.d/20apt-esm-hook.conf || true ls -l /etc/apt/sources.list.d sudo find /etc/apt/sources.list.d -type f -not -name 'ubuntu.sources' -delete -print sudo sed -i 's/priority:1/priority:9/' /etc/apt/apt-mirrors.txt; cat /etc/apt/apt-mirrors.txt From 772b3e110d038fb48ec7e3cfd4dccb34004da776 Mon Sep 17 00:00:00 2001 From: Viktor Szakats Date: Tue, 8 Sep 2026 09:36:50 +0200 Subject: [PATCH 070/481] GHA: start using `background: true` to parallelize job steps Saving around 40-60 seconds across 5 jobs. - checksrc: parallelize steps. - checksrc: run steps without tool dependencies first. - checksrc: install typos-cli in the background. - actionlint: add exceptions for background control keys. - checkdocs: prepare sources while installing tools. - checkdocs: cut verify-examples job time in half by running gcc and clang in parallel. - verify-examples: make it support parallel runs. Ref: https://github.blog/changelog/2026-06-25-actions-steps-can-now-be-run-in-parallel/ checksrc: Before: https://github.com/curl/curl/actions/runs/34202115545 After: https://github.com/curl/curl/actions/runs/34215398251 checkdocs: Before: https://github.com/curl/curl/actions/runs/34103387501 After: https://github.com/curl/curl/actions/runs/34215398288 Closes #22872 --- .github/scripts/verify-examples.pl | 6 +-- .github/workflows/checkdocs.yml | 35 ++++++++++------ .github/workflows/checksrc.yml | 66 +++++++++++++++++++++--------- 3 files changed, 73 insertions(+), 34 deletions(-) diff --git a/.github/scripts/verify-examples.pl b/.github/scripts/verify-examples.pl index 8aeda5ef7d03..fe576afd4df7 100755 --- a/.github/scripts/verify-examples.pl +++ b/.github/scripts/verify-examples.pl @@ -27,7 +27,7 @@ use warnings; my @files = @ARGV; -my $cfile = "test.c"; +my $cfile = 'test-' . (defined $ENV{'CC'} ? $ENV{'CC'} : 'gcc') . '.c'; my $check = "./scripts/checksrc.pl"; my $error = 0; @@ -42,14 +42,14 @@ sub testcompile { if(defined $ENV{'CFLAGS'}) { $cflags .= ' ' . $ENV{'CFLAGS'}; } - my $rc = system($cc . ' -c test.c -I include -W -Wall -pedantic -Werror ' . $cflags . ' ' . + my $rc = system($cc . ' -c ' . $cfile . ' -I include -W -Wall -pedantic -Werror ' . $cflags . ' ' . '-Wno-unused-parameter -Wno-unused-but-set-variable ' . '-DCURL_ALLOW_OLD_MULTI_SOCKET -DCURL_DISABLE_DEPRECATION') >> 8; return $rc; } sub checksrc { - my $rc = system($check, ('test.c')) >> 8; + my $rc = system($check, ($cfile)) >> 8; return $rc; } diff --git a/.github/workflows/checkdocs.yml b/.github/workflows/checkdocs.yml index 7eca7305ac86..3078954374b0 100644 --- a/.github/workflows/checkdocs.yml +++ b/.github/workflows/checkdocs.yml @@ -49,48 +49,53 @@ jobs: with: persist-credentials: false + - name: 'trim headers off all *.md files' + background: true + run: git ls-files '*.md' -z | xargs -0 -n1 .github/scripts/trimmarkdownheader.pl + - name: 'install prereqs' run: | python3 -m venv ~/venv ~/venv/bin/pip --disable-pip-version-check --no-input --no-cache-dir install --progress-bar off --prefer-binary -r .github/scripts/requirements-proselint.txt - name: 'trim headers off all *.md files' - run: git ls-files '*.md' -z | xargs -0 -n1 .github/scripts/trimmarkdownheader.pl + wait-all: - - name: 'check prose' + - name: 'check special prose' # For CHECKSRC and files with aggressive exclamation mark needs + background: true run: | - cat < ~/.proselintrc.json + cat < ~/.proselintrc-special.json { "checks": { "annotations.misc": false, "lexical_illusions": false, - "misc.annotations": false, - "redundancy.misc.garner": false, - "security.password": false, - "spelling.ve_of": false, "typography.diacritical_marks": false, + "typography.punctuation.exclamation": false, "typography.symbols": false } } JSON source ~/venv/bin/activate - git ls-files README '*.md' -z | grep -Evz '(CHECKSRC|DISTROS|CURLOPT_INTERFACE|interface)\.md' | xargs -0 proselint check -- + proselint check --config ~/.proselintrc-special.json docs/internals/CHECKSRC.md docs/libcurl/opts/CURLOPT_INTERFACE.md docs/cmdline-opts/interface.md - - name: 'check special prose' # For CHECKSRC and files with aggressive exclamation mark needs + - name: 'check prose' run: | - cat < ~/.proselintrc.json + cat < ~/.proselintrc-base.json { "checks": { "annotations.misc": false, "lexical_illusions": false, + "misc.annotations": false, + "redundancy.misc.garner": false, + "security.password": false, + "spelling.ve_of": false, "typography.diacritical_marks": false, - "typography.punctuation.exclamation": false, "typography.symbols": false } } JSON source ~/venv/bin/activate - proselint check docs/internals/CHECKSRC.md docs/libcurl/opts/CURLOPT_INTERFACE.md docs/cmdline-opts/interface.md + git ls-files README '*.md' -z | grep -Evz '(CHECKSRC|DISTROS|CURLOPT_INTERFACE|interface)\.md' | xargs -0 proselint check --config ~/.proselintrc-base.json -- pyspelling: name: 'pyspelling' @@ -101,6 +106,7 @@ jobs: persist-credentials: false - name: 'trim all *.md files in docs/' + background: true run: .github/scripts/cleancmd.pl 'docs/*.md' - name: 'install' @@ -113,6 +119,9 @@ jobs: python3 -m venv ~/venv ~/venv/bin/pip --disable-pip-version-check --no-input --no-cache-dir install --progress-bar off --prefer-binary -r .github/scripts/requirements-docs.txt + - name: 'trim all *.md files in docs/' + wait-all: + - name: 'check spelling' run: | source ~/venv/bin/activate @@ -131,9 +140,11 @@ jobs: persist-credentials: false - name: 'verify synopsis' + background: true run: .github/scripts/verify-synopsis.pl docs/libcurl/curl*.md - name: 'verify examples (gcc)' + background: true run: | cmake -B bld-gcc -G Ninja -D_CURL_PREFILL=ON -DCURL_USE_LIBPSL=OFF -DCURL_WERROR=ON -D_CURL_SAVE_PICKY_OPTIONS=ON export CFLAGS; CFLAGS="$(cat bld-gcc/picky-options.txt)" diff --git a/.github/workflows/checksrc.yml b/.github/workflows/checksrc.yml index 323a68369c2b..34e291221946 100644 --- a/.github/workflows/checksrc.yml +++ b/.github/workflows/checksrc.yml @@ -55,6 +55,23 @@ jobs: with: persist-credentials: false + - name: 'cmake options in documentation' + background: true + run: scripts/cmakeopts.sh + + - name: 'labelercheck' + background: true + run: .github/scripts/labelercheck.sh + + - name: 'perlcheck' + background: true + run: scripts/perlcheck.sh + + - name: 'install prereqs (typos)' + background: true + timeout-minutes: 2 + run: /home/linuxbrew/.linuxbrew/bin/brew install typos-cli + - name: 'install prereqs' run: | python3 -m venv ~/venv @@ -64,53 +81,50 @@ jobs: -r tests/requirements.txt - name: 'REUSE check' + background: true run: | source ~/venv/bin/activate reuse lint - name: 'cmakelint' + background: true run: | source ~/venv/bin/activate scripts/cmakelint.sh - - name: 'cmake options in documentation' - run: scripts/cmakeopts.sh - - - name: 'labelercheck' - run: .github/scripts/labelercheck.sh - - - name: 'perlcheck' - run: scripts/perlcheck.sh - - name: 'ruff' + background: true run: | source ~/venv/bin/activate scripts/pythonlint.sh - name: 'pyrefly' + background: true run: | source ~/venv/bin/activate cd tests/http/ pyrefly check --python-version=3.8 --preset=basic --check-unannotated-defs --summary + - name: 'wait-all' + wait-all: + - name: filter off code from all C and H files run: | - git ls-files -z '**.[ch]' | xargs -0 -r -n1 ./.github/scripts/c-strip - - - name: 'codespell' - run: | - source ~/venv/bin/activate - codespell --version - .github/scripts/codespell.sh + git ls-files -z '**.[ch]' | xargs -0 -r -n1 .github/scripts/c-strip - name: 'typos' - timeout-minutes: 2 + background: true run: | - /home/linuxbrew/.linuxbrew/bin/brew install typos-cli eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)" typos --version .github/scripts/typos.sh + - name: 'codespell' + run: | + source ~/venv/bin/activate + codespell --version + .github/scripts/codespell.sh + pytype: name: 'pytype' runs-on: ubuntu-24.04-arm # pytype is discontinued and requires python 3.8-3.12 @@ -194,6 +208,7 @@ jobs: run: sed -i.bak -E 's/shell\x3a .+ zizmor\x3a ignore.+$/shell\x3a bash/g' .github/workflows/*.yml - name: 'zizmor GHA' + background: true env: GH_TOKEN: '${{ secrets.GITHUB_TOKEN }}' run: | @@ -201,6 +216,7 @@ jobs: zizmor --persona pedantic .github/workflows/*.yml .github/dependabot.yml - name: 'zizmor GHA (auditor, warning-only)' + background: true env: GH_TOKEN: '${{ secrets.GITHUB_TOKEN }}' run: | @@ -208,28 +224,40 @@ jobs: zizmor --persona auditor .github/workflows/*.yml .github/dependabot.yml || true - name: 'actionlint' + background: true run: | eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)" export SHELLCHECK_OPTS='--exclude=1090,1091,2086,2153 --enable=avoid-nullary-conditions,deprecate-which' actionlint --version - actionlint --ignore matrix --ignore ubuntu-26.04 --ignore ubuntu-24.04-firewall .github/workflows/*.yml + actionlint --ignore matrix \ + --ignore ubuntu-26.04 \ + --ignore ubuntu-24.04-firewall \ + --ignore 'unexpected key "background"' \ + --ignore 'unexpected key "wait"' \ + --ignore 'unexpected key "wait-all"' \ + --ignore 'must run script with "run" section' \ + .github/workflows/*.yml - name: 'shellcheck CI' + background: true run: | eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)" shellcheck --version .github/scripts/shellcheck-ci.sh - name: 'shellcheck' + background: true run: | eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)" shellcheck --version .github/scripts/shellcheck.sh - name: 'spacecheck' + background: true run: scripts/spacecheck.pl - name: 'yamlcheck' + background: true run: .github/scripts/yamlcheck.sh - name: 'badwords' From f86897d831843762699a3c067e3882b551d078c8 Mon Sep 17 00:00:00 2001 From: Stefan Eissing Date: Tue, 8 Sep 2026 09:27:17 +0200 Subject: [PATCH 071/481] http2: changes in error handling - always treat errors from nghttp2_session_mem_recv() as fatal - always return 0 from invalid_frame callback, so that nghttp2's internal RST/GOAWAY handling can work - adjust test_01_14 return code expectation Closes #22870 --- lib/http2.c | 6 +++--- tests/http/test_01_basic.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/http2.c b/lib/http2.c index 0cfdf20571be..ff53aeab3bb5 100644 --- a/lib/http2.c +++ b/lib/http2.c @@ -496,7 +496,7 @@ static CURLcode h2_process_pending_input(struct Curl_cfilter *cf, rv = nghttp2_session_mem_recv(ctx->h2, (const uint8_t *)buf, blen); if(!curlx_sztouz(rv, &nread)) { failf(data, "nghttp2 recv error %zd: %s", rv, nghttp2_strerror((int)rv)); - return CURLE_HTTP2; + return CURLE_RECV_ERROR; } Curl_bufq_skip(&ctx->inbufq, nread); if(Curl_bufq_is_empty(&ctx->inbufq)) { @@ -1253,10 +1253,10 @@ static int cf_h2_on_invalid_frame_recv(nghttp2_session *session, stream->error = ngerr; stream->closed = TRUE; stream->reset = TRUE; - return 0; /* keep the connection alive */ } } - return NGHTTP2_ERR_CALLBACK_FAILURE; + /* Return no error, nghttp2 will RST/GOAWAY by itself when needed */ + return 0; } static int on_data_chunk_recv(nghttp2_session *session, uint8_t flags, diff --git a/tests/http/test_01_basic.py b/tests/http/test_01_basic.py index 3b7719fcce26..e89c1025d6df 100644 --- a/tests/http/test_01_basic.py +++ b/tests/http/test_01_basic.py @@ -213,7 +213,7 @@ def test_01_14_gigalarge_resp_headers(self, env: Env, httpd, configures_httpd, p f'/curltest/tweak?x-hd={256 * 1024}' r = curl.http_get(url=url, alpn_proto=proto, extra_args=[]) if proto == 'h2': - r.check_exit_code(16) # CURLE_HTTP2 + r.check_exit_code(56) # CURLE_RECV_ERROR else: r.check_exit_code(0) # 1.1 can do From 76847b05e3f709b9f5bcafa2a85c4600179a2095 Mon Sep 17 00:00:00 2001 From: Stefan Eissing Date: Tue, 8 Sep 2026 09:46:43 +0200 Subject: [PATCH 072/481] mbedtls: fix ticket handling Getting MBEDTLS_ERR_SSL_RECEIVED_NEW_SESSION_TICKET from mbedTLS is in no way blocking. Send/receive calls need to make another attempt afterwards to get the "real" result. Closes #22871 --- lib/vtls/mbedtls.c | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/lib/vtls/mbedtls.c b/lib/vtls/mbedtls.c index 3c83fe8f6e62..b332f465c576 100644 --- a/lib/vtls/mbedtls.c +++ b/lib/vtls/mbedtls.c @@ -1275,6 +1275,9 @@ static CURLcode mbed_send(struct Curl_cfilter *cf, struct Curl_easy *data, int nwritten; DEBUGASSERT(backend); +#ifdef MBEDTLS_SSL_PROTO_TLS1_3 +do_send: +#endif *pnwritten = 0; connssl->io_need = CURL_SSL_IO_NEED_NONE; /* mbedTLS is picky when a mbedtls_ssl_write() was previously blocked. @@ -1300,6 +1303,12 @@ static CURLcode mbed_send(struct Curl_cfilter *cf, struct Curl_easy *data, switch(nwritten) { #ifdef MBEDTLS_SSL_PROTO_TLS1_3 case MBEDTLS_ERR_SSL_RECEIVED_NEW_SESSION_TICKET: + result = mbed_new_session(cf, data); + /* This return code is not blocking. Having treated the new + * ticket, resume sending until we get a "real" result. */ + if(!result) + goto do_send; + break; #endif case MBEDTLS_ERR_SSL_WANT_READ: connssl->io_need = CURL_SSL_IO_NEED_RECV; @@ -1448,6 +1457,9 @@ static CURLcode mbed_recv(struct Curl_cfilter *cf, struct Curl_easy *data, int nread; DEBUGASSERT(backend); +#ifdef MBEDTLS_SSL_SESSION_TICKETS +do_read: +#endif *pnread = 0; connssl->io_need = CURL_SSL_IO_NEED_NONE; @@ -1461,8 +1473,12 @@ static CURLcode mbed_recv(struct Curl_cfilter *cf, struct Curl_easy *data, switch(nread) { #ifdef MBEDTLS_SSL_SESSION_TICKETS case MBEDTLS_ERR_SSL_RECEIVED_NEW_SESSION_TICKET: - mbed_new_session(cf, data); - FALLTHROUGH(); + result = mbed_new_session(cf, data); + /* This is not blocking anything. We can try again until a + * "real" result comes. */ + if(!result) + goto do_read; + break; #endif case MBEDTLS_ERR_SSL_WANT_READ: connssl->io_need = CURL_SSL_IO_NEED_RECV; From 06a3bda4f2a3e126d95fd17d0bbae3ab8193ccc8 Mon Sep 17 00:00:00 2001 From: Stefan Eissing Date: Tue, 8 Sep 2026 13:57:16 +0200 Subject: [PATCH 073/481] easy: make ca_cache_timeout standalone The `struct ssl_general_config` had only a single `int` member. Dissolve the struct and make the `int ssl_ca_cache_timeout` a direct member of `data->set`. Closes #22877 --- lib/setopt.c | 2 +- lib/url.c | 2 +- lib/urldata.h | 3 ++- lib/vtls/gtls.c | 5 ++--- lib/vtls/openssl.c | 7 +++---- lib/vtls/schannel.c | 5 ++--- lib/vtls/vtls_config.h | 4 ---- lib/vtls/wolfssl.c | 5 ++--- 8 files changed, 13 insertions(+), 20 deletions(-) diff --git a/lib/setopt.c b/lib/setopt.c index 0e15980b8ff8..7b911838d039 100644 --- a/lib/setopt.c +++ b/lib/setopt.c @@ -914,7 +914,7 @@ static CURLcode setopt_long_ssl(struct Curl_easy *data, CURLoption option, if(Curl_ssl_supports(data, SSLSUPP_CA_CACHE)) { result = value_range(&arg, -1, -1, INT_MAX); if(!result) - s->general_ssl.ca_cache_timeout = (int)arg; + s->ssl_ca_cache_timeout = (int)arg; } else result = CURLE_NOT_BUILT_IN; diff --git a/lib/url.c b/lib/url.c index 29fc79464a00..adf1fe221736 100644 --- a/lib/url.c +++ b/lib/url.c @@ -352,7 +352,7 @@ void Curl_init_userdefined(struct Curl_easy *data) set->dns_cache_timeout_ms = 60000; /* Timeout every 60 seconds by default */ /* Timeout every 24 hours by default */ - set->general_ssl.ca_cache_timeout = 24 * 60 * 60; + set->ssl_ca_cache_timeout = 24 * 60 * 60; set->httpauth = CURLAUTH_BASIC; /* defaults to basic */ diff --git a/lib/urldata.h b/lib/urldata.h index cd50c5135272..7d23f11eaf40 100644 --- a/lib/urldata.h +++ b/lib/urldata.h @@ -900,7 +900,6 @@ struct UserDefined { uint8_t proxytype; /* what kind of proxy */ uint8_t socks5auth;/* kind of SOCKS5 authentication to use (bitmask) */ #endif - struct ssl_general_config general_ssl; /* general user defined SSL stuff */ timediff_t dns_cache_timeout_ms; /* DNS cache timeout (milliseconds) */ uint32_t buffer_size; /* size of receive buffer to use */ uint32_t upload_buffer_size; /* size of upload buffer to use, keep it >= @@ -968,6 +967,8 @@ struct UserDefined { #ifndef CURL_DISABLE_SMTP struct curl_slist *mail_rcpt; /* linked list of mail recipients */ #endif + int ssl_ca_cache_timeout; /* Certificate store cache timeout (seconds) */ + int tcp_keepidle; /* seconds in idle before sending keepalive probe */ int tcp_keepintvl; /* seconds between TCP keepalive probes */ int tcp_keepcnt; /* maximum number of keepalive probes */ diff --git a/lib/vtls/gtls.c b/lib/vtls/gtls.c index ed7a010e5eb3..915c514fa840 100644 --- a/lib/vtls/gtls.c +++ b/lib/vtls/gtls.c @@ -570,9 +570,8 @@ static CURLcode gtls_populate_creds(struct Curl_cfilter *cf, static bool gtls_shared_creds_expired(struct Curl_easy *data, const struct gtls_shared_creds *sc) { - const struct ssl_general_config *cfg = &data->set.general_ssl; timediff_t elapsed_ms = curlx_ptimediff_ms(Curl_pgrs_now(data), &sc->time); - timediff_t timeout_ms = cfg->ca_cache_timeout * (timediff_t)1000; + timediff_t timeout_ms = data->set.ssl_ca_cache_timeout * (timediff_t)1000; if(timeout_ms < 0) return FALSE; @@ -663,7 +662,7 @@ CURLcode Curl_gtls_client_trust_setup(struct Curl_cfilter *cf, /* Consider the X509 store cacheable if it comes exclusively from a CAfile, or no source is provided and we are falling back to OpenSSL's built-in default. */ - cache_criteria_met = (data->set.general_ssl.ca_cache_timeout != 0) && + cache_criteria_met = (data->set.ssl_ca_cache_timeout != 0) && conn_config->verifypeer && !conn_config->CApath && !conn_config->ca_info_blob && diff --git a/lib/vtls/openssl.c b/lib/vtls/openssl.c index d43f1bdc9bbc..16c4b19c54b5 100644 --- a/lib/vtls/openssl.c +++ b/lib/vtls/openssl.c @@ -3176,12 +3176,11 @@ static void oss_x509_share_free(void *key, size_t key_len, void *p) static bool ossl_cached_x509_store_expired(struct Curl_easy *data, const struct ossl_x509_share *mb) { - const struct ssl_general_config *cfg = &data->set.general_ssl; - if(cfg->ca_cache_timeout < 0) + if(data->set.ssl_ca_cache_timeout < 0) return FALSE; else { timediff_t elapsed_ms = curlx_ptimediff_ms(Curl_pgrs_now(data), &mb->time); - timediff_t timeout_ms = cfg->ca_cache_timeout * (timediff_t)1000; + timediff_t timeout_ms = data->set.ssl_ca_cache_timeout * (timediff_t)1000; return elapsed_ms >= timeout_ms; } @@ -3292,7 +3291,7 @@ CURLcode Curl_ssl_setup_x509_store(struct Curl_cfilter *cf, /* Consider the X509 store cacheable if it comes exclusively from a CAfile, or no source is provided and we are falling back to OpenSSL's built-in default. */ - cache_criteria_met = (data->set.general_ssl.ca_cache_timeout != 0) && + cache_criteria_met = (data->set.ssl_ca_cache_timeout != 0) && conn_config->verifypeer && !conn_config->CApath && !conn_config->ca_info_blob && diff --git a/lib/vtls/schannel.c b/lib/vtls/schannel.c index 1cf1f7d5fca6..b51793412355 100644 --- a/lib/vtls/schannel.c +++ b/lib/vtls/schannel.c @@ -2727,7 +2727,6 @@ HCERTSTORE Curl_schannel_get_cached_cert_store(struct Curl_cfilter *cf, struct Curl_multi *multi = data->multi; const struct curl_blob *ca_info_blob = conn_config->ca_info_blob; struct schannel_cert_share *share; - const struct ssl_general_config *cfg = &data->set.general_ssl; timediff_t timeout_ms; unsigned char info_blob_digest[CURL_SHA256_DIGEST_LENGTH]; @@ -2745,14 +2744,14 @@ HCERTSTORE Curl_schannel_get_cached_cert_store(struct Curl_cfilter *cf, } /* zero ca_cache_timeout completely disables caching */ - if(!cfg->ca_cache_timeout) { + if(!data->set.ssl_ca_cache_timeout) { return NULL; } /* check for cache timeout by using the cached_x509_store_expired timediff calculation pattern from openssl.c. negative timeout means retain forever. */ - timeout_ms = cfg->ca_cache_timeout * (timediff_t)1000; + timeout_ms = data->set.ssl_ca_cache_timeout * (timediff_t)1000; if(timeout_ms >= 0) { timediff_t elapsed_ms = curlx_ptimediff_ms(Curl_pgrs_now(data), &share->time); diff --git a/lib/vtls/vtls_config.h b/lib/vtls/vtls_config.h index 4c061bc0fd6d..201b2c786f9e 100644 --- a/lib/vtls/vtls_config.h +++ b/lib/vtls/vtls_config.h @@ -78,10 +78,6 @@ struct ssl_config_data { BIT(custom_cablob); /* application has set custom CA blob */ }; -struct ssl_general_config { - int ca_cache_timeout; /* Certificate store cache timeout (seconds) */ -}; - void Curl_ssl_config_init(struct ssl_primary_config *sslc); void Curl_ssl_config_cleanup(struct ssl_primary_config *sslc); diff --git a/lib/vtls/wolfssl.c b/lib/vtls/wolfssl.c index 03e118249b65..eadf905123d5 100644 --- a/lib/vtls/wolfssl.c +++ b/lib/vtls/wolfssl.c @@ -697,9 +697,8 @@ static void wssl_x509_share_free(void *key, size_t key_len, void *p) static bool wssl_cached_x509_store_expired(struct Curl_easy *data, const struct wssl_x509_share *mb) { - const struct ssl_general_config *cfg = &data->set.general_ssl; timediff_t elapsed_ms = curlx_ptimediff_ms(Curl_pgrs_now(data), &mb->time); - timediff_t timeout_ms = cfg->ca_cache_timeout * (timediff_t)1000; + timediff_t timeout_ms = data->set.ssl_ca_cache_timeout * (timediff_t)1000; if(timeout_ms < 0) return FALSE; @@ -803,7 +802,7 @@ CURLcode Curl_wssl_setup_x509_store(struct Curl_cfilter *cf, /* Consider the X509 store cacheable if it comes exclusively from a CAfile, or no source is provided and we are falling back to wolfSSL's built-in default. */ - cache_criteria_met = (data->set.general_ssl.ca_cache_timeout != 0) && + cache_criteria_met = (data->set.ssl_ca_cache_timeout != 0) && conn_config->verifypeer && !conn_config->CApath && !conn_config->ca_info_blob && From 337a405511ff8ad4b0345cc2566b5c8487e5e8c5 Mon Sep 17 00:00:00 2001 From: Stefan Eissing Date: Tue, 8 Sep 2026 10:37:35 +0200 Subject: [PATCH 074/481] conncache: remove bundle dest We stored the "destination" string at each connection bundle, but it was never necessary to have it for operations. Remove the duplicated string from the bundle. Closes #22873 --- lib/conncache.c | 46 ++++++++++++++++++++-------------------------- 1 file changed, 20 insertions(+), 26 deletions(-) diff --git a/lib/conncache.c b/lib/conncache.c index 5de4b75a1dbb..bd94d9d5439b 100644 --- a/lib/conncache.c +++ b/lib/conncache.c @@ -62,21 +62,13 @@ /* A list of connections to the same destination. */ struct cpool_bundle { struct Curl_llist conns; /* connections in the bundle */ - size_t dest_len; /* total length of destination, including NUL */ - char dest[1]; /* destination of bundle, allocated to keep dest_len bytes */ }; -static struct cpool_bundle *cpool_bundle_create(const char *dest) +static struct cpool_bundle *cpool_bundle_create(void) { - struct cpool_bundle *bundle; - size_t dest_len = strlen(dest) + 1; - - bundle = curlx_calloc(1, sizeof(*bundle) + dest_len - 1); - if(!bundle) - return NULL; - Curl_llist_init(&bundle->conns, NULL); - bundle->dest_len = dest_len; - memcpy(bundle->dest, dest, bundle->dest_len); + struct cpool_bundle *bundle = curlx_calloc(1, sizeof(*bundle)); + if(bundle) + Curl_llist_init(&bundle->conns, NULL); return bundle; } @@ -141,18 +133,19 @@ static struct connectdata *cpool_get_first(struct cpool *cpool) } static struct cpool_bundle *cpool_find_bundle(struct cpool *cpool, - struct connectdata *conn) + const char *destination) { - return Curl_hash_pick(&cpool->dest2bundle, - conn->destination, strlen(conn->destination) + 1); + return Curl_hash_pick( + &cpool->dest2bundle, CURL_UNCONST(destination), strlen(destination) + 1); } static void cpool_remove_bundle(struct cpool *cpool, - struct cpool_bundle *bundle) + const char *destination) { if(!cpool) return; - Curl_hash_delete(&cpool->dest2bundle, bundle->dest, bundle->dest_len); + Curl_hash_delete(&cpool->dest2bundle, + CURL_UNCONST(destination), strlen(destination) + 1); } static void cpool_remove_conn(struct cpool *cpool, @@ -162,11 +155,11 @@ static void cpool_remove_conn(struct cpool *cpool, DEBUGASSERT(cpool); if(list) { /* The connection is certainly in the pool, but where? */ - struct cpool_bundle *bundle = cpool_find_bundle(cpool, conn); + struct cpool_bundle *bundle = cpool_find_bundle(cpool, conn->destination); if(bundle && (list == &bundle->conns)) { cpool_bundle_remove(bundle, conn); if(!Curl_llist_count(&bundle->conns)) - cpool_remove_bundle(cpool, bundle); + cpool_remove_bundle(cpool, conn->destination); conn->bits.in_cpool = FALSE; cpool->num_conn--; } @@ -301,16 +294,17 @@ void Curl_cpool_xfer_init(struct Curl_easy *data) } static struct cpool_bundle *cpool_add_bundle(struct cpool *cpool, - struct connectdata *conn) + const char *destination) { struct cpool_bundle *bundle; - bundle = cpool_bundle_create(conn->destination); + bundle = cpool_bundle_create(); if(!bundle) return NULL; if(!Curl_hash_add(&cpool->dest2bundle, - bundle->dest, bundle->dest_len, bundle)) { + CURL_UNCONST(destination), strlen(destination) + 1, + bundle)) { cpool_bundle_destroy(bundle); return NULL; } @@ -486,7 +480,7 @@ int Curl_cpool_check_limits(struct Curl_easy *data, if(dest_limit) { size_t live; - bundle = cpool_find_bundle(cpool, conn); + bundle = cpool_find_bundle(cpool, conn->destination); live = bundle ? Curl_llist_count(&bundle->conns) : 0; shutdowns = multi ? Curl_cshutdn_dest_count(&multi->cshutdn, conn->destination) : 0; @@ -514,7 +508,7 @@ int Curl_cpool_check_limits(struct Curl_easy *data, cpool_evict_conn(cpool, admin, oldest_idle); /* in case the bundle was destroyed in disconnect, look it up again */ - bundle = cpool_find_bundle(cpool, conn); + bundle = cpool_find_bundle(cpool, conn->destination); live = bundle ? Curl_llist_count(&bundle->conns) : 0; } shutdowns = multi ? @@ -572,9 +566,9 @@ CURLcode Curl_cpool_add(struct Curl_easy *data, return CURLE_FAILED_INIT; CPOOL_LOCK(cpool, data); - bundle = cpool_find_bundle(cpool, conn); + bundle = cpool_find_bundle(cpool, conn->destination); if(!bundle) { - bundle = cpool_add_bundle(cpool, conn); + bundle = cpool_add_bundle(cpool, conn->destination); if(!bundle) { result = CURLE_OUT_OF_MEMORY; goto out; From b3e1bfcc46fff3f5a207b981ce32bb16770dcc76 Mon Sep 17 00:00:00 2001 From: Viktor Szakats Date: Tue, 8 Sep 2026 02:14:55 +0200 Subject: [PATCH 075/481] GHA: share apt/brew/pip install logic between workflows, related tidy-ups Via a local custom/composite Action. To de-duplicate logic, and allow maintaining a single copy. It may also allow for more elaborate workarounds for package install issues, if it becomes necessary. Also: - tried using `background: true` to parellize apt/brew/pip installs, but it's not supported in custom Actions. - shellcheck-ci: run on composite actions. - checksrc: run zizmor on composite actions. (actionlint does not support them) - checksrc: run zizmor on dependabot YAML. - tidy up install prereq step names. - shellcheck-ci.sh: make it runnable from any directory. - drop `ca-certificates` Homebrew workaround. Not necessary at the moment. Follow-up to c155cbc6380467487fcbca126366638149f04cf0 #22438 - GHA/label: move egress firewall test from GHA/Linux/aws-lc. The firewall runner machine does not support the `$/` local action syntax. Follow-up to e5e1c356107ea4f8208757deb69776d94496cfae #22864 Refs: https://github.blog/changelog/2026-07-30-reference-same-repository-actions-with-self-repository-syntax/ https://docs.github.com/en/actions/tutorials/create-actions/create-a-composite-action https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-syntax#example-using-an-action-in-the-same-repository-as-the-workflow Ref: #22074 (first attempt) Closes #22867 --- .github/actions/pkg-install/action.yml | 65 +++++++++++++++++++++++ .github/scripts/shellcheck-ci.sh | 4 +- .github/workflows/checkdocs.yml | 19 +++---- .github/workflows/checksrc.yml | 57 ++++++++++---------- .github/workflows/codeql.yml | 14 ++--- .github/workflows/configure-vs-cmake.yml | 29 +++------- .github/workflows/distcheck.yml | 23 ++++---- .github/workflows/http3-linux.yml | 43 ++++++--------- .github/workflows/label.yml | 2 +- .github/workflows/linux.yml | 49 ++++++----------- .github/workflows/macos.yml | 67 ++++++------------------ .github/workflows/non-native.yml | 10 ++-- .github/workflows/windows.yml | 13 ++--- 13 files changed, 185 insertions(+), 210 deletions(-) create mode 100644 .github/actions/pkg-install/action.yml diff --git a/.github/actions/pkg-install/action.yml b/.github/actions/pkg-install/action.yml new file mode 100644 index 000000000000..66633984bd69 --- /dev/null +++ b/.github/actions/pkg-install/action.yml @@ -0,0 +1,65 @@ +# Copyright (C) Daniel Stenberg, , et al. +# +# SPDX-License-Identifier: curl + +# https://docs.github.com/en/actions/tutorials/create-actions/create-a-composite-action +name: 'Install packages' +description: 'Installs packages for local workflows' + +inputs: + apt: + description: 'Optional space-separated list of apt package names' + brew: + description: 'Optional space-separated list of Homebrew package names' + pip: + description: 'Optional pip command-line options' + +runs: + using: 'composite' + steps: + - name: 'install (apt)' + if: ${{ inputs.apt }} + shell: bash + env: + PACKAGES: ${{ inputs.apt }} + run: | + sudo rm -f /etc/apt/apt.conf.d/20apt-esm-hook.conf || true + ls -l /etc/apt/sources.list.d + sudo find /etc/apt/sources.list.d -type f -not -name 'ubuntu.sources' -delete -print + sudo sed -i 's/priority:1/priority:9/' /etc/apt/apt-mirrors.txt; cat /etc/apt/apt-mirrors.txt + [[ "${PACKAGES}" = *':i386'* ]] && sudo dpkg --add-architecture i386 + sudo apt-get -o Dpkg::Use-Pty=0 update + sudo apt-get -o Dpkg::Use-Pty=0 install ${PACKAGES} + + - name: 'install (brew)' + if: ${{ inputs.brew }} + shell: bash + env: + HOMEBREW_NO_INSTALL_CLEANUP: '1' + PACKAGES: ${{ inputs.brew }} + run: | + if [[ "$(uname)" != *'Darwin'* ]]; then + brewroot='/home/linuxbrew/.linuxbrew/bin/' + fi + # Run this command with retries because of spurious failures seen while running the tests. + # shellcheck disable=SC2181 + while [[ $? == 0 ]]; do + for i in 1 2 3; do + if ${brewroot}brew install ${PACKAGES}; then + break 2 + else + echo "Error: wait to try again: $i" + sleep 10 + fi + done + false Too many retries + done + + - name: 'install (pip)' + if: ${{ inputs.pip }} + shell: bash + env: + PACKAGES: ${{ inputs.pip }} + run: | + [ -d ~/venv ] || python3 -m venv ~/venv + ~/venv/bin/pip --disable-pip-version-check --no-input --no-cache-dir install --progress-bar off --prefer-binary ${PACKAGES} diff --git a/.github/scripts/shellcheck-ci.sh b/.github/scripts/shellcheck-ci.sh index 87e03b9d12b8..5b6b6c7235e5 100755 --- a/.github/scripts/shellcheck-ci.sh +++ b/.github/scripts/shellcheck-ci.sh @@ -7,10 +7,12 @@ set -eu +cd -- "$(dirname "${0}")"/../.. + export SHELLCHECK_OPTS='--exclude=1090,1091,2086,2153 --enable=avoid-nullary-conditions,deprecate-which' # GHA -git ls-files '.github/workflows/*.yml' | while read -r f; do +git ls-files '.github/actions/**/*.yml' '.github/workflows/*.yml' | while read -r f; do echo "Verifying ${f}..." { echo '#!/usr/bin/env bash' diff --git a/.github/workflows/checkdocs.yml b/.github/workflows/checkdocs.yml index 3078954374b0..26397ea6b55e 100644 --- a/.github/workflows/checkdocs.yml +++ b/.github/workflows/checkdocs.yml @@ -54,9 +54,9 @@ jobs: run: git ls-files '*.md' -z | xargs -0 -n1 .github/scripts/trimmarkdownheader.pl - name: 'install prereqs' - run: | - python3 -m venv ~/venv - ~/venv/bin/pip --disable-pip-version-check --no-input --no-cache-dir install --progress-bar off --prefer-binary -r .github/scripts/requirements-proselint.txt + uses: $/.github/actions/pkg-install + with: + pip: -r .github/scripts/requirements-proselint.txt - name: 'trim headers off all *.md files' wait-all: @@ -109,15 +109,12 @@ jobs: background: true run: .github/scripts/cleancmd.pl 'docs/*.md' - - name: 'install' + - name: 'install prereqs' timeout-minutes: 2 - run: | - sudo find /etc/apt/sources.list.d -type f -not -name 'ubuntu.sources' -delete -print - sudo sed -i 's/priority:1/priority:9/' /etc/apt/apt-mirrors.txt; cat /etc/apt/apt-mirrors.txt - sudo apt-get -o Dpkg::Use-Pty=0 update - sudo apt-get -o Dpkg::Use-Pty=0 install aspell aspell-en - python3 -m venv ~/venv - ~/venv/bin/pip --disable-pip-version-check --no-input --no-cache-dir install --progress-bar off --prefer-binary -r .github/scripts/requirements-docs.txt + uses: $/.github/actions/pkg-install + with: + apt: aspell aspell-en + pip: -r .github/scripts/requirements-docs.txt - name: 'trim all *.md files in docs/' wait-all: diff --git a/.github/workflows/checksrc.yml b/.github/workflows/checksrc.yml index 34e291221946..c7b65a31d351 100644 --- a/.github/workflows/checksrc.yml +++ b/.github/workflows/checksrc.yml @@ -33,7 +33,6 @@ permissions: {} env: DO_NOT_TRACK: '1' - HOMEBREW_NO_INSTALL_CLEANUP: '1' jobs: checksrc: @@ -67,17 +66,19 @@ jobs: background: true run: scripts/perlcheck.sh - - name: 'install prereqs (typos)' + - name: 'install typos-cli' background: true timeout-minutes: 2 - run: /home/linuxbrew/.linuxbrew/bin/brew install typos-cli + uses: $/.github/actions/pkg-install + with: + brew: typos-cli - name: 'install prereqs' - run: | - python3 -m venv ~/venv - ~/venv/bin/pip --disable-pip-version-check --no-input --no-cache-dir install --progress-bar off --prefer-binary \ - -r .github/scripts/requirements.txt \ - -r tests/http/requirements.txt \ + uses: $/.github/actions/pkg-install + with: + pip: | + -r .github/scripts/requirements.txt + -r tests/http/requirements.txt -r tests/requirements.txt - name: 'REUSE check' @@ -134,11 +135,12 @@ jobs: persist-credentials: false - name: 'install prereqs' - run: | - python3 -m venv ~/venv - ~/venv/bin/pip --disable-pip-version-check --no-input --no-cache-dir install --progress-bar off --prefer-binary pytype==2024.10.11 \ - -r .github/scripts/requirements.txt \ - -r tests/http/requirements.txt \ + uses: $/.github/actions/pkg-install + with: + pip: | + pytype==2024.10.11 + -r .github/scripts/requirements.txt + -r tests/http/requirements.txt -r tests/requirements.txt - name: 'check' @@ -153,12 +155,9 @@ jobs: steps: - name: 'install pmccabe' timeout-minutes: 2 - run: | - ls -l /etc/apt/sources.list.d - sudo find /etc/apt/sources.list.d -type f -not -name 'ubuntu.sources' -delete -print - sudo sed -i 's/priority:1/priority:9/' /etc/apt/apt-mirrors.txt; cat /etc/apt/apt-mirrors.txt - sudo apt-get -o Dpkg::Use-Pty=0 update - sudo apt-get -o Dpkg::Use-Pty=0 install pmccabe + uses: $/.github/actions/pkg-install + with: + apt: pmccabe - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: @@ -177,11 +176,9 @@ jobs: steps: - name: 'install prereqs' timeout-minutes: 2 - run: | - sudo find /etc/apt/sources.list.d -type f -not -name 'ubuntu.sources' -delete -print - sudo sed -i 's/priority:1/priority:9/' /etc/apt/apt-mirrors.txt; cat /etc/apt/apt-mirrors.txt - sudo apt-get -o Dpkg::Use-Pty=0 update - sudo apt-get -o Dpkg::Use-Pty=0 install libxml2-utils + uses: $/.github/actions/pkg-install + with: + apt: libxml2-utils - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: @@ -197,7 +194,9 @@ jobs: steps: - name: 'install prereqs' timeout-minutes: 2 - run: /home/linuxbrew/.linuxbrew/bin/brew install actionlint shellcheck zizmor + uses: $/.github/actions/pkg-install + with: + brew: actionlint shellcheck zizmor - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: @@ -213,7 +212,7 @@ jobs: GH_TOKEN: '${{ secrets.GITHUB_TOKEN }}' run: | eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)" - zizmor --persona pedantic .github/workflows/*.yml .github/dependabot.yml + git ls-files '.github/actions/**/*.yml' '.github/dependabot.yml' '.github/workflows/*.yml' -z | xargs -0 -r zizmor --persona pedantic -- - name: 'zizmor GHA (auditor, warning-only)' background: true @@ -221,7 +220,7 @@ jobs: GH_TOKEN: '${{ secrets.GITHUB_TOKEN }}' run: | eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)" - zizmor --persona auditor .github/workflows/*.yml .github/dependabot.yml || true + git ls-files '.github/actions/**/*.yml' '.github/dependabot.yml' '.github/workflows/*.yml' -z | xargs -0 -r zizmor --persona auditor -- || true - name: 'actionlint' background: true @@ -229,13 +228,15 @@ jobs: eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)" export SHELLCHECK_OPTS='--exclude=1090,1091,2086,2153 --enable=avoid-nullary-conditions,deprecate-which' actionlint --version - actionlint --ignore matrix \ + actionlint \ + --ignore matrix \ --ignore ubuntu-26.04 \ --ignore ubuntu-24.04-firewall \ --ignore 'unexpected key "background"' \ --ignore 'unexpected key "wait"' \ --ignore 'unexpected key "wait-all"' \ --ignore 'must run script with "run" section' \ + --ignore 'invalid format because ref is missing' \ .github/workflows/*.yml - name: 'shellcheck CI' diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 03aa9ba9d6e4..18a2dd27f9f7 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -37,7 +37,6 @@ permissions: {} env: DO_NOT_TRACK: '1' - HOMEBREW_NO_INSTALL_CLEANUP: '1' jobs: gha_python: @@ -76,15 +75,10 @@ jobs: - name: 'install prereqs' if: ${{ matrix.platform == 'Linux' }} timeout-minutes: 2 - run: | - sudo find /etc/apt/sources.list.d -type f -not -name 'ubuntu.sources' -delete -print - sudo sed -i 's/priority:1/priority:9/' /etc/apt/apt-mirrors.txt; cat /etc/apt/apt-mirrors.txt - sudo apt-get -o Dpkg::Use-Pty=0 update - sudo apt-get -o Dpkg::Use-Pty=0 install \ - libpsl-dev libbrotli-dev libidn2-dev libssh2-1-dev libssh-dev \ - libnghttp2-dev libldap-dev libkrb5-dev libgnutls28-dev libwolfssl-dev - /home/linuxbrew/.linuxbrew/bin/brew install ca-certificates || true - /home/linuxbrew/.linuxbrew/bin/brew install c-ares gsasl libnghttp3 libngtcp2 mbedtls rustls-ffi + uses: $/.github/actions/pkg-install + with: + apt: libpsl-dev libbrotli-dev libidn2-dev libssh2-1-dev libssh-dev libnghttp2-dev libldap-dev libkrb5-dev libgnutls28-dev libwolfssl-dev + brew: c-ares gsasl libnghttp3 libngtcp2 mbedtls rustls-ffi - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: diff --git a/.github/workflows/configure-vs-cmake.yml b/.github/workflows/configure-vs-cmake.yml index 1195878e0915..73dfabb6d9dc 100644 --- a/.github/workflows/configure-vs-cmake.yml +++ b/.github/workflows/configure-vs-cmake.yml @@ -41,7 +41,6 @@ permissions: {} env: CURL_CI: github DO_NOT_TRACK: '1' - HOMEBREW_NO_INSTALL_CLEANUP: '1' jobs: check-linux: @@ -88,21 +87,11 @@ jobs: name: 'macOS' runs-on: macos-latest steps: - - name: 'install packages' + - name: 'install prereqs' timeout-minutes: 2 - run: | - # shellcheck disable=SC2181 - while [[ $? == 0 ]]; do - for i in 1 2 3; do - if brew install automake libtool; then - break 2 - else - echo "Error: wait to try again: $i" - sleep 10 - fi - done - false Too many retries - done + uses: $/.github/actions/pkg-install + with: + brew: automake libtool - name: 'toolchain versions' run: echo '::group::brew packages installed'; ls -l /opt/homebrew/opt; echo '::endgroup::' @@ -155,13 +144,11 @@ jobs: env: TRIPLET: 'x86_64-w64-mingw32' steps: - - name: 'install packages' + - name: 'install prereqs' timeout-minutes: 1 - run: | - sudo find /etc/apt/sources.list.d -type f -not -name 'ubuntu.sources' -delete -print - sudo sed -i 's/priority:1/priority:9/' /etc/apt/apt-mirrors.txt; cat /etc/apt/apt-mirrors.txt - sudo apt-get -o Dpkg::Use-Pty=0 update - sudo apt-get -o Dpkg::Use-Pty=0 install gcc-mingw-w64-x86-64-win32 + uses: $/.github/actions/pkg-install + with: + apt: gcc-mingw-w64-x86-64-win32 - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: diff --git a/.github/workflows/distcheck.yml b/.github/workflows/distcheck.yml index f8936b1cbe6a..65bef132dac0 100644 --- a/.github/workflows/distcheck.yml +++ b/.github/workflows/distcheck.yml @@ -24,7 +24,6 @@ permissions: {} env: CURL_TEST_MIN: 1500 DO_NOT_TRACK: '1' - HOMEBREW_NO_INSTALL_CLEANUP: '1' MAKEFLAGS: -j 5 jobs: @@ -279,31 +278,31 @@ jobs: update: false cache: false path-type: inherit - install: >- - perl mingw-w64-x86_64-zlib mingw-w64-x86_64-zstd mingw-w64-x86_64-libpsl mingw-w64-x86_64-libssh2 mingw-w64-x86_64-nghttp2 mingw-w64-x86_64-openssl + install: perl mingw-w64-x86_64-zlib mingw-w64-x86_64-zstd mingw-w64-x86_64-libpsl mingw-w64-x86_64-libssh2 mingw-w64-x86_64-nghttp2 mingw-w64-x86_64-openssl - name: 'install prereqs' - timeout-minutes: 3 + if: ${{ !contains(matrix.image, 'windows') }} + timeout-minutes: 2 + uses: $/.github/actions/pkg-install + with: + apt: ${{ contains(matrix.image, 'ubuntu') && 'libpsl-dev libssl-dev' || '' }} + brew: ${{ contains(matrix.image, 'macos') && 'libpsl openssl' || '' }} + + - name: 'install prereqs (cmake)' + timeout-minutes: 2 run: | + cd ~ if [[ "${MATRIX_IMAGE}" = *'windows'* ]]; then - cd ~ curl --disable --fail --silent --show-error --connect-timeout 15 --max-time 60 --retry 3 --retry-connrefused \ --location --proto-redir =https "https://github.com/Kitware/CMake/releases/download/v${OLD_CMAKE_VERSION}/cmake-${OLD_CMAKE_VERSION}-win64-x64.zip" --output pkg.bin sha256sum pkg.bin && sha256sum pkg.bin | grep -qwF -- "${OLD_CMAKE_SHA256_WIN_INTEL}" && unzip -q pkg.bin && rm -f pkg.bin printf '%s' ~/cmake-"${OLD_CMAKE_VERSION}"-win64-x64/bin/cmake.exe > ~/old-cmake-path.txt elif [[ "${MATRIX_IMAGE}" = *'ubuntu'* ]]; then - sudo find /etc/apt/sources.list.d -type f -not -name 'ubuntu.sources' -delete -print - sudo sed -i 's/priority:1/priority:9/' /etc/apt/apt-mirrors.txt; cat /etc/apt/apt-mirrors.txt - sudo apt-get -o Dpkg::Use-Pty=0 update - sudo apt-get -o Dpkg::Use-Pty=0 install libpsl-dev libssl-dev - cd ~ curl --disable --fail --silent --show-error --connect-timeout 15 --max-time 60 --retry 3 --retry-connrefused \ --location --proto-redir =https "https://github.com/Kitware/CMake/releases/download/v${OLD_CMAKE_VERSION}/cmake-${OLD_CMAKE_VERSION}-Linux-aarch64.tar.gz" --output pkg.bin sha256sum pkg.bin | tee /dev/stderr | grep -qwF -- "${OLD_CMAKE_SHA256_LINUX_ARM}" && tar -xzf pkg.bin && rm -f pkg.bin printf '%s' ~/cmake-"${OLD_CMAKE_VERSION}"-Linux-aarch64/bin/cmake > ~/old-cmake-path.txt else - brew install libpsl openssl - cd ~ curl --disable --fail --silent --show-error --connect-timeout 15 --max-time 60 --retry 3 --retry-connrefused \ --location --proto-redir =https "https://github.com/Kitware/CMake/releases/download/v${OLD_CMAKE_VERSION}/cmake-${OLD_CMAKE_VERSION}-macos-universal.tar.gz" --output pkg.bin sha256sum pkg.bin | tee /dev/stderr | grep -qwF -- "${OLD_CMAKE_SHA256_MACOS_UNI}" && tar -xzf pkg.bin && rm -f pkg.bin diff --git a/.github/workflows/http3-linux.yml b/.github/workflows/http3-linux.yml index 59077b9ae96f..c595c5d422bd 100644 --- a/.github/workflows/http3-linux.yml +++ b/.github/workflows/http3-linux.yml @@ -223,16 +223,14 @@ jobs: - name: 'install build prereqs' if: ${{ steps.settings.outputs.needs-build }} timeout-minutes: 2 - run: | - sudo find /etc/apt/sources.list.d -type f -not -name 'ubuntu.sources' -delete -print - sudo sed -i 's/priority:1/priority:9/' /etc/apt/apt-mirrors.txt; cat /etc/apt/apt-mirrors.txt - sudo apt-get -o Dpkg::Use-Pty=0 update - sudo apt-get -o Dpkg::Use-Pty=0 install \ - libtool autoconf automake pkgconf \ - libbrotli-dev libzstd-dev zlib1g-dev \ - libev-dev \ - libuv1-dev \ - libc-ares-dev \ + uses: $/.github/actions/pkg-install + with: + apt: >- + libtool autoconf automake pkgconf + libbrotli-dev libzstd-dev zlib1g-dev + libev-dev + libuv1-dev + libc-ares-dev libp11-kit-dev autopoint bison gperf gtk-doc-tools libtasn1-bin # for GnuTLS - name: 'build awslc' @@ -433,7 +431,6 @@ jobs: env: CURL_TRACE_PKG_CONFIG: '1' MATRIX_BUILD: ${{ matrix.build.generate && 'cmake' || 'autotools' }} - MATRIX_INSTALL_PACKAGES: '${{ matrix.build.install_packages }}' strategy: fail-fast: false matrix: @@ -578,21 +575,15 @@ jobs: steps: - name: 'install prereqs' timeout-minutes: 2 - env: - INSTALL_PACKAGES: >- + uses: $/.github/actions/pkg-install + with: + apt: >- + libtool autoconf automake pkgconf + libpsl-dev libbrotli-dev libzstd-dev zlib1g-dev libidn2-0-dev libldap-dev libuv1-dev valgrind ${{ !contains(matrix.build.install_steps, 'skipall') && !contains(matrix.build.install_steps, 'skiprun') && 'stunnel4 ' || '' }} ${{ !contains(matrix.build.install_steps, 'skipall') && !contains(matrix.build.install_steps, 'skiprun') && 'apache2 apache2-dev libnghttp2-dev vsftpd dante-server libev-dev' || '' }} - - run: | - sudo find /etc/apt/sources.list.d -type f -not -name 'ubuntu.sources' -delete -print - sudo sed -i 's/priority:1/priority:9/' /etc/apt/apt-mirrors.txt; cat /etc/apt/apt-mirrors.txt - sudo apt-get -o Dpkg::Use-Pty=0 update - sudo apt-get -o Dpkg::Use-Pty=0 install \ - libtool autoconf automake pkgconf \ - libpsl-dev libbrotli-dev libzstd-dev zlib1g-dev libidn2-0-dev libldap-dev libuv1-dev valgrind \ - ${INSTALL_PACKAGES} \ - ${MATRIX_INSTALL_PACKAGES} + ${{ matrix.build.install_packages }} - name: 'cache awslc' if: ${{ contains(matrix.build.name, 'awslc') }} @@ -883,9 +874,9 @@ jobs: - name: 'install pytest prereqs' if: ${{ !contains(matrix.build.install_steps, 'skipall') && !contains(matrix.build.install_steps, 'skiprun') }} - run: | - [ -d ~/venv ] || python3 -m venv ~/venv - ~/venv/bin/pip --disable-pip-version-check --no-input --no-cache-dir install --progress-bar off --prefer-binary -r tests/http/requirements.txt + uses: $/.github/actions/pkg-install + with: + pip: -r tests/http/requirements.txt - name: 'run pytest (event based)' if: ${{ !contains(matrix.build.install_steps, 'skipall') && !contains(matrix.build.install_steps, 'skiprun') }} diff --git a/.github/workflows/label.yml b/.github/workflows/label.yml index fa06c826ac5a..9ef543db2f5c 100644 --- a/.github/workflows/label.yml +++ b/.github/workflows/label.yml @@ -24,7 +24,7 @@ env: jobs: label: name: 'Labeler' - runs-on: ubuntu-slim + runs-on: ubuntu-24.04-firewall permissions: contents: read # To comply with https://github.com/actions/labeler documentation pull-requests: write # To edit labels on PRs diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 754011fce04f..34be80e8e4e0 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -36,7 +36,6 @@ env: CURL_CI: github CURL_TEST_MIN: 1660 DO_NOT_TRACK: '1' - HOMEBREW_NO_INSTALL_CLEANUP: '1' # renovate: datasource=github-tags depName=awslabs/aws-lc versioning=semver registryUrl=https://github.com AWSLC_VERSION: 5.5.0 # renovate: datasource=github-tags depName=google/boringssl versioning=semver registryUrl=https://github.com @@ -81,7 +80,6 @@ jobs: configure: --with-openssl=/home/runner/awslc --enable-ech --enable-ntlm --enable-smb - name: 'awslc' - image: ubuntu-24.04-firewall install_packages: libidn2-dev install_steps: awslc clean generate: -DOPENSSL_ROOT_DIR=/home/runner/awslc -DUSE_ECH=ON -DCMAKE_UNITY_BUILD=OFF -DCURL_DROP_UNUSED=ON -DCURL_PATCHSTAMP=test-patch -DCURL_ENABLE_NTLM=ON @@ -468,40 +466,25 @@ jobs: - name: 'install prereqs' if: ${{ matrix.build.container == null && !contains(matrix.build.name, 'i686') }} timeout-minutes: 3 - env: - INSTALL_PACKAGES_BREW: '${{ matrix.build.install_steps_brew }}' - INSTALL_PACKAGES: >- + uses: $/.github/actions/pkg-install + with: + brew: ${{ matrix.build.install_steps_brew }} + apt: >- + libtool autoconf automake pkgconf + libpsl-dev zlib1g-dev libbrotli-dev libzstd-dev ${{ !contains(matrix.build.install_steps, 'skipall') && !contains(matrix.build.install_steps, 'skiprun') && 'stunnel4' || '' }} ${{ contains(matrix.build.install_steps, 'pytest') && 'apache2 apache2-dev libnghttp2-dev vsftpd dante-server' || '' }} - - run: | - sudo rm -f /etc/apt/apt.conf.d/20apt-esm-hook.conf || true - ls -l /etc/apt/sources.list.d - sudo find /etc/apt/sources.list.d -type f -not -name 'ubuntu.sources' -delete -print - sudo sed -i 's/priority:1/priority:9/' /etc/apt/apt-mirrors.txt; cat /etc/apt/apt-mirrors.txt - sudo apt-get -o Dpkg::Use-Pty=0 update - sudo apt-get -o Dpkg::Use-Pty=0 install \ - libtool autoconf automake pkgconf \ - libpsl-dev zlib1g-dev libbrotli-dev libzstd-dev \ - ${INSTALL_PACKAGES} \ - ${MATRIX_INSTALL_PACKAGES} - if [ -n "${INSTALL_PACKAGES_BREW}" ]; then - /home/linuxbrew/.linuxbrew/bin/brew install ca-certificates || true - /home/linuxbrew/.linuxbrew/bin/brew install ${INSTALL_PACKAGES_BREW} - fi + ${{ matrix.build.install_packages }} - name: 'install prereqs (i686)' if: ${{ contains(matrix.build.name, 'i686') }} timeout-minutes: 2 - run: | - sudo find /etc/apt/sources.list.d -type f -not -name 'ubuntu.sources' -delete -print - sudo sed -i 's/priority:1/priority:9/' /etc/apt/apt-mirrors.txt; cat /etc/apt/apt-mirrors.txt - sudo dpkg --add-architecture i386 - sudo apt-get -o Dpkg::Use-Pty=0 update - sudo apt-get -o Dpkg::Use-Pty=0 install \ - libtool autoconf automake pkgconf stunnel4 \ - libpsl-dev:i386 libbrotli-dev:i386 libzstd-dev:i386 \ - ${MATRIX_INSTALL_PACKAGES} + uses: $/.github/actions/pkg-install + with: + apt: >- + libtool autoconf automake pkgconf stunnel4 + libpsl-dev:i386 libbrotli-dev:i386 libzstd-dev:i386 + ${{ matrix.build.install_packages }} - name: 'install prereqs (alpine)' if: ${{ startsWith(matrix.build.container, 'alpine') }} @@ -986,9 +969,9 @@ jobs: - name: 'install pytest prereqs' if: ${{ contains(matrix.build.install_steps, 'pytest') }} - run: | - [ -d ~/venv ] || python3 -m venv ~/venv - ~/venv/bin/pip --disable-pip-version-check --no-input --no-cache-dir install --progress-bar off --prefer-binary -r tests/http/requirements.txt + uses: $/.github/actions/pkg-install + with: + pip: -r tests/http/requirements.txt - name: 'run pytest' if: ${{ contains(matrix.build.install_steps, 'pytest') }} diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index c2441d5de81a..a26dc6781a0c 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -48,7 +48,6 @@ env: CURL_CI: github CURL_TEST_MIN: 1750 DO_NOT_TRACK: '1' - HOMEBREW_NO_INSTALL_CLEANUP: '1' MAKEFLAGS: -j 4 LDFLAGS: -w # suppress 'object file was built for newer macOS version than being linked' warnings @@ -88,22 +87,12 @@ jobs: -DCURL_USE_LIBPSL=OFF -DCURL_ENABLE_NTLM=ON steps: - - name: 'brew install' + - name: 'install prereqs' if: ${{ matrix.build.configure }} timeout-minutes: 5 - run: | - # shellcheck disable=SC2181 - while [[ $? == 0 ]]; do - for i in 1 2 3; do - if brew install automake libtool; then - break 2 - else - echo "Error: wait to try again: $i" - sleep 10 - fi - done - false Too many retries - done + uses: $/.github/actions/pkg-install + with: + brew: automake libtool - name: 'toolchain versions' run: | @@ -233,7 +222,6 @@ jobs: CC: '${{ matrix.build.compiler }}' MATRIX_BUILD: ${{ matrix.build.generate && 'cmake' || 'autotools' }} MATRIX_COMPILER: '${{ matrix.build.compiler }}' - MATRIX_INSTALL: '${{ matrix.build.install }}' MATRIX_INSTALL_STEPS: '${{ matrix.build.install_steps }}' MATRIX_MACOS_VERSION_MIN: '${{ matrix.build.macos-version-min }}' strategy: @@ -419,31 +407,16 @@ jobs: brew unlink openssl fi - - name: 'brew install' + - name: 'install prereqs' timeout-minutes: 5 - # Run this command with retries because of spurious failures seen - # while running the tests, for example - # https://github.com/curl/curl/runs/4095721123?check_suite_focus=true - env: - INSTALL_PACKAGES: >- + uses: $/.github/actions/pkg-install + with: + brew: >- + pkgconf libpsl libssh2 ${{ matrix.build.install }} ${{ matrix.build.generate && 'ninja' || 'automake libtool' }} ${{ !contains(matrix.build.install_steps, 'skipall') && !contains(matrix.build.install_steps, 'skiprun') && 'nghttp2 stunnel' || '' }} ${{ contains(matrix.build.install_steps, 'pytest') && 'caddy httpd vsftpd' || '' }} - run: | - # shellcheck disable=SC2181 - while [[ $? == 0 ]]; do - for i in 1 2 3; do - if brew install pkgconf libpsl libssh2 ${INSTALL_PACKAGES} ${MATRIX_INSTALL}; then - break 2 - else - echo "Error: wait to try again: $i" - sleep 10 - fi - done - false Too many retries - done - - name: 'toolchain versions' run: | [[ "${MATRIX_COMPILER}" = 'llvm'* ]] && CC="$(brew --prefix "${MATRIX_COMPILER}")/bin/clang" @@ -613,9 +586,9 @@ jobs: - name: 'install pytest prereqs' if: ${{ contains(matrix.build.install_steps, 'pytest') }} - run: | - [ -d ~/venv ] || python3 -m venv ~/venv - ~/venv/bin/pip --disable-pip-version-check --no-input --no-cache-dir install --progress-bar off --prefer-binary -r tests/http/requirements.txt + uses: $/.github/actions/pkg-install + with: + pip: -r tests/http/requirements.txt - name: 'run pytest' if: ${{ contains(matrix.build.install_steps, 'pytest') }} @@ -698,19 +671,9 @@ jobs: steps: - name: 'install autotools' if: ${{ matrix.build == 'autotools' }} - run: | - # shellcheck disable=SC2181 - while [[ $? == 0 ]]; do - for i in 1 2 3; do - if brew install automake libtool; then - break 2 - else - echo "Error: wait to try again: $i" - sleep 10 - fi - done - false Too many retries - done + uses: $/.github/actions/pkg-install + with: + brew: automake libtool - name: 'toolchain versions' run: | diff --git a/.github/workflows/non-native.yml b/.github/workflows/non-native.yml index b025aa9ea180..3e0ec7129f50 100644 --- a/.github/workflows/non-native.yml +++ b/.github/workflows/non-native.yml @@ -489,13 +489,11 @@ jobs: build: [autotools, cmake] fail-fast: false steps: - - name: 'install packages' + - name: 'install prereqs' timeout-minutes: 2 - run: | - sudo find /etc/apt/sources.list.d -type f -not -name 'ubuntu.sources' -delete -print - sudo sed -i 's/priority:1/priority:9/' /etc/apt/apt-mirrors.txt; cat /etc/apt/apt-mirrors.txt - sudo apt-get -o Dpkg::Use-Pty=0 update - sudo apt-get -o Dpkg::Use-Pty=0 install libfl2 + uses: $/.github/actions/pkg-install + with: + apt: libfl2 - name: 'cache compiler (djgpp)' uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 73ec97abc1b2..e625a5e68a49 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -817,16 +817,11 @@ jobs: - { build: 'cmake' , compiler: 'gcc' } - { build: 'cmake' , compiler: 'clang-tidy', install_packages: 'clang-22 clang-tidy-22', CFLAGS: '-Wunused-macros' } steps: - - name: 'install packages' + - name: 'install prereqs' timeout-minutes: 2 - env: - MATRIX_INSTALL_PACKAGES: '${{ matrix.install_packages }}' - run: | - ls -l /etc/apt/sources.list.d - sudo find /etc/apt/sources.list.d -type f -not -name 'ubuntu.sources' -delete -print - sudo sed -i 's/priority:1/priority:9/' /etc/apt/apt-mirrors.txt; cat /etc/apt/apt-mirrors.txt - sudo apt-get -o Dpkg::Use-Pty=0 update - sudo apt-get -o Dpkg::Use-Pty=0 install gcc-mingw-w64-x86-64-win32 ${MATRIX_INSTALL_PACKAGES} + uses: $/.github/actions/pkg-install + with: + apt: gcc-mingw-w64-x86-64-win32 ${{ matrix.install_packages }} - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: From b8172ada19b1211aeed64f473b4a53e941c6fabe Mon Sep 17 00:00:00 2001 From: Daniel Stenberg Date: Tue, 8 Sep 2026 18:02:11 +0200 Subject: [PATCH 076/481] GHA: fix yaml comment in http3-linux.yml Codex Security insists that this was not a valid yaml comment but the words were instead passed as arguments to the command. Closes #22880 --- .github/workflows/http3-linux.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/http3-linux.yml b/.github/workflows/http3-linux.yml index c595c5d422bd..ba5303b3064c 100644 --- a/.github/workflows/http3-linux.yml +++ b/.github/workflows/http3-linux.yml @@ -231,7 +231,7 @@ jobs: libev-dev libuv1-dev libc-ares-dev - libp11-kit-dev autopoint bison gperf gtk-doc-tools libtasn1-bin # for GnuTLS + libp11-kit-dev autopoint bison gperf gtk-doc-tools libtasn1-bin - name: 'build awslc' if: ${{ !steps.cache-awslc.outputs.cache-hit }} From 08d50a791b3197a3d0a4fc2f778e243f5501c67e Mon Sep 17 00:00:00 2001 From: Daniel Stenberg Date: Tue, 8 Sep 2026 16:04:42 +0200 Subject: [PATCH 077/481] hsts: cap max-age to two years And ignore problems saving individual HSTS entries - a single entry problem should not affect storing other entries. Added test 1862 to verify. Closes #22878 --- lib/hsts.c | 25 +++++++-------- tests/data/Makefile.am | 2 +- tests/data/test1862 | 73 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 86 insertions(+), 14 deletions(-) create mode 100644 tests/data/test1862 diff --git a/lib/hsts.c b/lib/hsts.c index ef4128fd64d1..1cce1e5fb950 100644 --- a/lib/hsts.c +++ b/lib/hsts.c @@ -43,6 +43,8 @@ #define MAX_HSTS_DATELEN 17 #define UNLIMITED "unlimited" +#define CAP_HSTS_MAX_AGE (2*365*24*3600) /* two years cap */ + #if defined(DEBUGBUILD) || defined(UNITTESTS) /* to play well with debug builds, we can *set* a fixed time this will return */ @@ -257,9 +259,9 @@ CURLcode Curl_hsts_parse(struct hsts *h, const char *hostname, const char *vp = curlx_str(&val); if(gotma) return CURLE_BAD_FUNCTION_ARGUMENT; - rc = curlx_str_number(&vp, &expires, TIME_T_MAX); + rc = curlx_str_number(&vp, &expires, CAP_HSTS_MAX_AGE); if(rc == STRE_OVERFLOW) - expires = CURL_OFF_T_MAX; + expires = CAP_HSTS_MAX_AGE; else if(rc) /* invalid max-age */ return CURLE_BAD_FUNCTION_ARGUMENT; @@ -344,22 +346,21 @@ static CURLcode hsts_push(struct Curl_easy *data, /* * Write this single hsts entry to a single output line */ -static CURLcode hsts_out(struct stsentry *sts, FILE *fp) +static void hsts_out(struct stsentry *sts, FILE *fp) { struct tm stamp; if(sts->expires != TIME_T_MAX) { CURLcode result = curlx_gmtime((time_t)sts->expires, &stamp); - if(result) - return result; - curl_mfprintf(fp, "%s%s \"%d%02d%02d %02d:%02d:%02d\"\n", - sts->includeSubDomains ? "." : "", sts->host, - stamp.tm_year + 1900, stamp.tm_mon + 1, stamp.tm_mday, - stamp.tm_hour, stamp.tm_min, stamp.tm_sec); + if(!result) + /* skip the entry if the date function fails */ + curl_mfprintf(fp, "%s%s \"%d%02d%02d %02d:%02d:%02d\"\n", + sts->includeSubDomains ? "." : "", sts->host, + stamp.tm_year + 1900, stamp.tm_mon + 1, stamp.tm_mday, + stamp.tm_hour, stamp.tm_min, stamp.tm_sec); } else curl_mfprintf(fp, "%s%s \"%s\"\n", sts->includeSubDomains ? "." : "", sts->host, UNLIMITED); - return CURLE_OK; } /* @@ -394,9 +395,7 @@ CURLcode Curl_hsts_save(struct Curl_easy *data, struct hsts *h, for(e = Curl_llist_head(&h->list); e; e = n) { struct stsentry *sts = Curl_node_elem(e); n = Curl_node_next(e); - result = hsts_out(sts, out); - if(result) - break; + hsts_out(sts, out); } curlx_fclose(out); if(!result && tempstore && curlx_rename(tempstore, file)) diff --git a/tests/data/Makefile.am b/tests/data/Makefile.am index 54e675fa28c8..d9574a84304b 100644 --- a/tests/data/Makefile.am +++ b/tests/data/Makefile.am @@ -1741,7 +1741,7 @@ TESTCASES = \ test1849 \ test1850 \ test1851 \ - \ + test1862 \ test1900 \ test1901 \ test1902 \ diff --git a/tests/data/test1862 b/tests/data/test1862 new file mode 100644 index 000000000000..b9a29f6376d7 --- /dev/null +++ b/tests/data/test1862 @@ -0,0 +1,73 @@ + + + + +HTTP +HSTS + + + + + + +HTTP/1.1 200 OK +Date: Tue, 09 Nov 2010 14:49:00 GMT +Content-Length: 6 +Connection: close +Strict-Transport-Security: max-age=1152921504606846976 + +-foo- + + + + + +http + + +HSTS +Debug + + +# November 11, 2030 + +CURL_HSTS_HTTP=yes +CURL_TIME=1920582000 + + + +HSTS with extreme max-age + + +http://this.hsts.example:%HTTPPORT/%TESTNUMBER --hsts %LOGDIR/input%TESTNUMBER --resolve this.hsts.example:%HTTPPORT:%HOSTIP + + +# Your HSTS cache. https://curl.se/docs/hsts.html +# This file was generated by libcurl! Edit at your own risk. +somehere.example "20330525 03:33:20" +elsewhere.example.com "20330727 03:33:20" + + +test-duphandle + + + + +# we let it CONNECT to the server to confirm HSTS but deny from there + +GET /1862 HTTP/1.1 +Host: this.hsts.example:%HTTPPORT +User-Agent: curl/%VERSION +Accept: */* + + + +# Your HSTS cache. https://curl.se/docs/hsts.html +# This file was generated by libcurl! Edit at your own risk. +somehere.example "20330525 03:33:20" +elsewhere.example.com "20330727 03:33:20" +this.hsts.example "20321109 23:00:00" + + + + From 8903f4ce8e3334368eeb7a79a7f1d493f4d0806b Mon Sep 17 00:00:00 2001 From: Stefan Eissing Date: Tue, 8 Sep 2026 12:48:30 +0200 Subject: [PATCH 078/481] urldata: remove conn->given The `connectdata` member `given` is always the same as `conn->origin->scheme`. It never changes. Remove that member and always use the origin scheme instead. Closes #22875 --- lib/http2.c | 2 +- lib/http_httpsig.c | 2 +- lib/url.c | 17 +++++++++-------- lib/urldata.h | 1 - 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/lib/http2.c b/lib/http2.c index ff53aeab3bb5..f4abd550ff86 100644 --- a/lib/http2.c +++ b/lib/http2.c @@ -1440,7 +1440,7 @@ static int on_header(nghttp2_session *session, const nghttp2_frame *frame, /* no memory */ return NGHTTP2_ERR_CALLBACK_FAILURE; if(!curl_strequal(check, (const char *)value) && - ((data->state.origin->port != cf->conn->given->defport) || + ((data->state.origin->port != cf->conn->origin->scheme->defport) || !curl_strequal(data->state.origin->hostname, (const char *)value))) { /* This is push is not for the same authority that was asked for in * the URL. RFC 7540 section 8.2 says: "A client MUST treat a diff --git a/lib/http_httpsig.c b/lib/http_httpsig.c index 37f23a09cbe5..804018081e32 100644 --- a/lib/http_httpsig.c +++ b/lib/http_httpsig.c @@ -157,7 +157,7 @@ static CURLcode httpsig_authority(struct Curl_easy *data, const char *hostname = conn->origin->hostname; uint16_t port = conn->origin->port; - if((conn->given->defport != port) && port) + if((conn->origin->scheme->defport != port) && port) return curlx_dyn_addf(authority_buf, "%s:%u", hostname, port); return curlx_dyn_add(authority_buf, hostname); } diff --git a/lib/url.c b/lib/url.c index adf1fe221736..d848eba09d4f 100644 --- a/lib/url.c +++ b/lib/url.c @@ -1230,17 +1230,17 @@ static struct connectdata *allocate_conn(struct Curl_easy *data, } static CURLcode url_set_conn_scheme(struct Curl_easy *data, - struct connectdata *conn, - const struct Curl_scheme *scheme) + struct connectdata *conn) { /* URL scheme is usable for connection when it is * - allowed * - not from a redirect or an allowed redirect protocol */ + const struct Curl_scheme *scheme = conn->origin->scheme; if(scheme->run && (data->set.allowed_protocols & scheme->protocol) && (!data->state.this_is_a_follow || (data->set.redir_protocols & scheme->protocol))) { - conn->scheme = conn->given = scheme; + conn->scheme = scheme; return CURLE_OK; } if(scheme->flags & PROTOPT_NO_TRANSFER) @@ -1517,7 +1517,7 @@ static CURLcode url_set_conn_origin_etc(struct Curl_easy *data, Curl_peer_link(&conn->origin, data->state.origin); /* set the connection scheme */ - result = url_set_conn_scheme(data, conn, conn->origin->scheme); + result = url_set_conn_scheme(data, conn); if(result) goto out; @@ -2313,15 +2313,16 @@ static CURLcode url_find_or_create_conn(struct Curl_easy *data, /* We attached an existing connection for this transfer. Copy * over transfer specific properties over from needle. */ struct connectdata *conn = data->conn; - VERBOSE(bool tls_upgraded = (!(needle->given->flags & PROTOPT_SSL) && - Curl_conn_is_ssl(conn, FIRSTSOCKET))); + VERBOSE(bool tls_upgraded = + (!(needle->origin->scheme->flags & PROTOPT_SSL) && + Curl_conn_is_ssl(conn, FIRSTSOCKET))); conn->bits.reuse = TRUE; url_conn_reuse_adjust(data, needle); #ifndef CURL_DISABLE_PROXY infof(data, "Reusing existing %s: connection%s with %s %s", - conn->given->name, + conn->origin->scheme->name, tls_upgraded ? " (upgraded to SSL)" : "", (conn->socks_proxy.peer || conn->http_proxy.peer) ? "proxy" : "host", conn->socks_proxy.peer ? conn->socks_proxy.peer->user_hostname : @@ -2329,7 +2330,7 @@ static CURLcode url_find_or_create_conn(struct Curl_easy *data, conn->origin->hostname); #else infof(data, "Reusing existing %s: connection%s with host %s", - conn->given->name, + conn->origin->scheme->name, tls_upgraded ? " (upgraded to SSL)" : "", conn->origin->hostname); #endif diff --git a/lib/urldata.h b/lib/urldata.h index 7d23f11eaf40..39a43738ad3f 100644 --- a/lib/urldata.h +++ b/lib/urldata.h @@ -279,7 +279,6 @@ struct connectdata { struct Curl_creds *creds; /* When connection itself is tied to credentials */ struct Curl_peer *creds_origin; /* origin tied credentials are for */ const struct Curl_scheme *scheme; /* Connection's real protocol handler */ - const struct Curl_scheme *given; /* The protocol first given */ /* `meta_hash` is a general key-value store for implementations * with the lifetime of the connection. From 19eec5f639d7d3f1c12883527a33e45e6d96cd53 Mon Sep 17 00:00:00 2001 From: Stefan Eissing Date: Tue, 8 Sep 2026 13:23:22 +0200 Subject: [PATCH 079/481] ssl: dedup ssl_fsslctx The members fsslctx(p) in `struct ssl_config_data` were only set on `data->set.ssl` and never on `data->set.proxy_ssl`, yet were used from the first one in all TLS filters that support it. Move the callback and its userdata outside the config struct, since we need it only once. Closes #22876 --- lib/setopt.c | 4 ++-- lib/urldata.h | 2 ++ lib/vdns/doh.c | 8 ++++---- lib/vtls/mbedtls.c | 6 +++--- lib/vtls/openssl.c | 6 +++--- lib/vtls/vtls_config.h | 2 -- lib/vtls/wolfssl.c | 6 +++--- 7 files changed, 17 insertions(+), 17 deletions(-) diff --git a/lib/setopt.c b/lib/setopt.c index 7b911838d039..0ac22a603d27 100644 --- a/lib/setopt.c +++ b/lib/setopt.c @@ -1935,7 +1935,7 @@ static CURLcode setopt_cptr_ssl(struct Curl_easy *data, CURLoption option, * Set an SSL_CTX callback parameter pointer */ if(Curl_ssl_supports(data, SSLSUPP_SSL_CTX)) { - data->set.ssl.fsslctxp = ptr; + data->set.ssl_fsslctxp = ptr; break; } else @@ -2626,7 +2626,7 @@ static CURLcode setopt_func(struct Curl_easy *data, CURLoption option, */ #ifdef USE_SSL if(Curl_ssl_supports(data, SSLSUPP_SSL_CTX)) { - s->ssl.fsslctx = va_arg(param, curl_ssl_ctx_callback); + s->ssl_fsslctx = va_arg(param, curl_ssl_ctx_callback); break; } else diff --git a/lib/urldata.h b/lib/urldata.h index 39a43738ad3f..8161a794abd4 100644 --- a/lib/urldata.h +++ b/lib/urldata.h @@ -890,6 +890,8 @@ struct UserDefined { the hostname and port to connect to */ time_t timevalue; /* what time to compare with */ struct ssl_config_data ssl; /* user defined SSL stuff */ + curl_ssl_ctx_callback ssl_fsslctx; /* function to initialize SSL ctx */ + void *ssl_fsslctxp; /* parameter for callback */ #ifndef CURL_DISABLE_PROXY struct ssl_config_data proxy_ssl; /* user defined SSL stuff for proxy */ struct curl_slist *proxyheaders; /* linked list of extra CONNECT headers */ diff --git a/lib/vdns/doh.c b/lib/vdns/doh.c index 3873c2459fe8..007585f88ea8 100644 --- a/lib/vdns/doh.c +++ b/lib/vdns/doh.c @@ -353,10 +353,10 @@ static CURLcode doh_probe_run(struct Curl_easy *data, } if(data->set.ssl.certinfo) ERROR_CHECK_SETOPT(CURLOPT_CERTINFO, 1L); - if(data->set.ssl.fsslctx) - ERROR_CHECK_SETOPT(CURLOPT_SSL_CTX_FUNCTION, data->set.ssl.fsslctx); - if(data->set.ssl.fsslctxp) - ERROR_CHECK_SETOPT(CURLOPT_SSL_CTX_DATA, data->set.ssl.fsslctxp); + if(data->set.ssl_fsslctx) + ERROR_CHECK_SETOPT(CURLOPT_SSL_CTX_FUNCTION, data->set.ssl_fsslctx); + if(data->set.ssl_fsslctxp) + ERROR_CHECK_SETOPT(CURLOPT_SSL_CTX_DATA, data->set.ssl_fsslctxp); if(CURL_EASY_STR(data, STRING_SSL_EC_CURVES)) { ERROR_CHECK_SETOPT(CURLOPT_SSL_EC_CURVES, CURL_EASY_STR(data, STRING_SSL_EC_CURVES)); diff --git a/lib/vtls/mbedtls.c b/lib/vtls/mbedtls.c index b332f465c576..9678cb1fc606 100644 --- a/lib/vtls/mbedtls.c +++ b/lib/vtls/mbedtls.c @@ -1018,9 +1018,9 @@ static CURLcode mbed_configure_ssl(struct Curl_cfilter *cf, #endif /* give application a chance to interfere with mbedTLS set up. */ - if(data->set.ssl.fsslctx) { - result = (*data->set.ssl.fsslctx)(data, &backend->config, - data->set.ssl.fsslctxp); + if(data->set.ssl_fsslctx) { + result = (*data->set.ssl_fsslctx)(data, &backend->config, + data->set.ssl_fsslctxp); if(result) failf(data, "error signaled by SSL ctx callback"); } diff --git a/lib/vtls/openssl.c b/lib/vtls/openssl.c index 16c4b19c54b5..8e4eae5f47ce 100644 --- a/lib/vtls/openssl.c +++ b/lib/vtls/openssl.c @@ -3946,7 +3946,7 @@ CURLcode Curl_ossl_ctx_init(struct ossl_ctx *octx, } /* give application a chance to interfere with SSL set up. */ - if(data->set.ssl.fsslctx) { + if(data->set.ssl_fsslctx) { struct Curl_mapi_guard guard; /* When a user callback is installed to modify the SSL_CTX, * we need to do the full initialization before calling it. @@ -3958,8 +3958,8 @@ CURLcode Curl_ossl_ctx_init(struct ossl_ctx *octx, octx->x509_store_setup = TRUE; } CURL_CBAPI_START(&guard, data, easy_fsslctx); - result = (*data->set.ssl.fsslctx)(data, octx->ssl_ctx, - data->set.ssl.fsslctxp); + result = (*data->set.ssl_fsslctx)(data, octx->ssl_ctx, + data->set.ssl_fsslctxp); CURL_CBAPI_END(&guard); if(result) { failf(data, "error signaled by SSL ctx callback"); diff --git a/lib/vtls/vtls_config.h b/lib/vtls/vtls_config.h index 201b2c786f9e..5872a13cdf0f 100644 --- a/lib/vtls/vtls_config.h +++ b/lib/vtls/vtls_config.h @@ -64,8 +64,6 @@ struct ssl_primary_config { struct ssl_config_data { struct ssl_primary_config primary; long certverifyresult; /* result from the certificate verification */ - curl_ssl_ctx_callback fsslctx; /* function to initialize SSL ctx */ - void *fsslctxp; /* parameter for call back */ BIT(certinfo); /* gather lots of certificate info */ BIT(earlydata); /* use TLS 1.3 early data */ BIT(enable_beast); /* allow this flaw for interoperability's sake */ diff --git a/lib/vtls/wolfssl.c b/lib/vtls/wolfssl.c index eadf905123d5..2bf01df600a5 100644 --- a/lib/vtls/wolfssl.c +++ b/lib/vtls/wolfssl.c @@ -1418,14 +1418,14 @@ CURLcode Curl_wssl_ctx_init(struct wssl_ctx *wctx, } /* give application a chance to interfere with SSL set up. */ - if(data->set.ssl.fsslctx) { + if(data->set.ssl_fsslctx) { if(!wctx->x509_store_setup) { result = Curl_wssl_setup_x509_store(cf, data, wctx); if(result) goto out; } - result = (*data->set.ssl.fsslctx)(data, wctx->ssl_ctx, - data->set.ssl.fsslctxp); + result = (*data->set.ssl_fsslctx)(data, wctx->ssl_ctx, + data->set.ssl_fsslctxp); if(result) { failf(data, "error signaled by SSL ctx callback"); goto out; From 323c184b6bdf5b1988a10c0a61560666e1588149 Mon Sep 17 00:00:00 2001 From: Bobbeh Rhino Date: Tue, 8 Sep 2026 06:33:27 -0400 Subject: [PATCH 080/481] libssh2: match hashed known_hosts entries by host libssh2 does not expose the plaintext name for hashed entries. Check each entry key against the requested hostname before using its key type. Otherwise an unrelated hashed entry can select the wrong algorithm. Add an SFTP regression test 3500 with an unrelated hashed entry before the requested host. Closes #22874 --- lib/vssh/libssh2.c | 16 +++++++-- tests/data/Makefile.am | 2 ++ tests/data/test3500 | 47 +++++++++++++++++++++++++ tests/libtest/Makefile.am | 3 +- tests/libtest/test3500.pl | 72 +++++++++++++++++++++++++++++++++++++++ 5 files changed, 136 insertions(+), 4 deletions(-) create mode 100644 tests/data/test3500 create mode 100755 tests/libtest/test3500.pl diff --git a/lib/vssh/libssh2.c b/lib/vssh/libssh2.c index a528e1e4e621..f09db6156fb4 100644 --- a/lib/vssh/libssh2.c +++ b/lib/vssh/libssh2.c @@ -685,9 +685,19 @@ static CURLcode ssh_force_knownhost_key_type(struct Curl_easy *data, break; } } - else { - found = TRUE; - break; + else if(store->key) { + int keycheck = libssh2_knownhost_checkp( + sshc->kh, conn->origin->hostname, + (conn->origin->port != PORT_SSH) ? conn->origin->port : -1, + store->key, strlen(store->key), + LIBSSH2_KNOWNHOST_TYPE_PLAIN | + LIBSSH2_KNOWNHOST_KEYENC_BASE64 | + (store->typemask & LIBSSH2_KNOWNHOST_KEY_MASK), + NULL); + if(keycheck == LIBSSH2_KNOWNHOST_CHECK_MATCH) { + found = TRUE; + break; + } } } } diff --git a/tests/data/Makefile.am b/tests/data/Makefile.am index d9574a84304b..1cb738496dd1 100644 --- a/tests/data/Makefile.am +++ b/tests/data/Makefile.am @@ -2100,6 +2100,8 @@ TESTCASES = \ test3400 \ test3401 \ \ + test3500 \ + \ test4000 \ test4001 \ test4644 \ diff --git a/tests/data/test3500 b/tests/data/test3500 new file mode 100644 index 000000000000..1bcf3bc17d31 --- /dev/null +++ b/tests/data/test3500 @@ -0,0 +1,47 @@ + + + + +SFTP +known_hosts +libssh2 + + + +# Server-side + + +Test data +for hashed known hosts + + + +# Client-side + + +sftp + + +sftp +libssh2 +!badlibssh + + +%PERL %SRCDIR/libtest/test3500.pl %LOGDIR/server/curl_host_key.pub %LOGDIR/known%TESTNUMBER "[%HOSTIP]:%SSHPORT" + + +SFTP matches requested hashed known_hosts entry + + +--knownhosts %LOGDIR/known%TESTNUMBER --key %LOGDIR/server/curl_client_key --pubkey %LOGDIR/server/curl_client_key.pub -u %USER: sftp://%HOSTIP:%SSHPORT%SFTP_PWD/%LOGDIR/file%TESTNUMBER.txt + + +Test data +for hashed known hosts + + + +# Verify data after the test has been "shot" + + + diff --git a/tests/libtest/Makefile.am b/tests/libtest/Makefile.am index 74eb150742c7..8fdf484b5592 100644 --- a/tests/libtest/Makefile.am +++ b/tests/libtest/Makefile.am @@ -49,7 +49,8 @@ EXTRA_DIST = CMakeLists.txt $(FIRST_C) $(FIRST_H) $(UTILS_C) $(UTILS_H) $(TESTS_ test1022.pl \ test307.pl \ test610.pl \ - test613.pl + test613.pl \ + test3500.pl CFLAGS += @CURL_CFLAG_EXTRAS@ diff --git a/tests/libtest/test3500.pl b/tests/libtest/test3500.pl new file mode 100755 index 000000000000..d95f54cecfcb --- /dev/null +++ b/tests/libtest/test3500.pl @@ -0,0 +1,72 @@ +#!/usr/bin/env perl +#*************************************************************************** +# _ _ ____ _ +# Project ___| | | | _ \| | +# / __| | | | |_) | | +# | (__| |_| | _ <| |___ +# \___|\___/|_| \_\_____| +# +# Copyright (C) Daniel Stenberg, , et al. +# +# This software is licensed as described in the file COPYING, which +# you should have received as part of this distribution. The terms +# are also available at https://curl.se/docs/copyright.html. +# +# You may opt to use, copy, modify, merge, publish, distribute and/or sell +# copies of the Software, and permit persons to whom the Software is +# furnished to do so, under the terms of the COPYING file. +# +# This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY +# KIND, either express or implied. +# +# SPDX-License-Identifier: curl +# +########################################################################### +use strict; +use warnings; + +use Digest::SHA qw(hmac_sha1); +use MIME::Base64 qw(encode_base64); + +sub errout { + print $_[0] . "\n"; + exit 1; +} + +if(@ARGV != 3) { + errout "Usage: $0 host-public-key known-hosts-file target"; +} + +my ($hostkey_file, $knownhosts_file, $target) = @ARGV; +open(my $hostkey, "<", $hostkey_file) or errout "$!"; +my $hostkey_line = <$hostkey>; +close($hostkey) or errout "$!"; + +my ($hostkey_algo, $hostkey_data) = split(/\s+/, $hostkey_line); +if(!$hostkey_algo || !$hostkey_data) { + errout "Failed parsing SSH host public key"; +} + +my $rsa_key = + "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCgSyqrUP2xhqLqkyopVapoKeucYIp" . + "ZXeUqj+Q9WLWyXka5re62Y3R/lxZUXtkJjlpdD2LNMFenNlDQvPaXH5Utllt+c3z" . + "wOkOLy7RXX5S37SfQFm/0fPkZqoZfMDB6XDcwA4cakjCRn4oZyKs8dWAxoNaTtYc" . + "nKKMJ6rmc8PttykJw9fwiKAjPyYf21/BMvWIzu9req+TrDA/Jcd2UAug94CikJIX" . + "uwKmSYleAOo4eJWx8GyujgEgZn05eq+K1LSg9RqRVIxOdqjoE5K3abBfs0rmUuLd" . + "MFh7nHseTFXYbKzpz46V7iZMfFuoyURrzVsqGUm+/m7dxBM98In12kOm3"; +my $ed25519_key = + "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAqmXRY8k+W9GtpWxYru6ELAXYHdXq" . + "jf33ziITGTiJUN"; +my $other_key = $hostkey_algo eq "ssh-ed25519" ? $rsa_key : $ed25519_key; + +my $salt = "curl-knownhosts-test"; +my $salt64 = encode_base64($salt, ""); +my $hash64 = encode_base64(hmac_sha1("unrelated.invalid", $salt), ""); +my $target_hash64 = encode_base64(hmac_sha1($target, $salt), ""); + +open(my $knownhosts, ">", $knownhosts_file) or errout "$!"; +print $knownhosts "|1|$salt64|$hash64 $other_key\n"; +print $knownhosts "|1|$salt64|$target_hash64 $hostkey_algo $hostkey_data\n"; +close($knownhosts) or errout "$!"; + +exit 0; From 9572e7914a4566e5356a096cd7df2d2634417eab Mon Sep 17 00:00:00 2001 From: Viktor Szakats Date: Wed, 9 Sep 2026 10:18:18 +0200 Subject: [PATCH 081/481] shellcheck-ci.sh: drop redundant GHA expression filter GHA expressions are no longer used and no longer allowed in GHA scripts. Follow-up to 17a669426f36b467dfd945b4b35f6211598b7977 #17537 Closes #22883 --- .github/scripts/shellcheck-ci.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/scripts/shellcheck-ci.sh b/.github/scripts/shellcheck-ci.sh index 5b6b6c7235e5..fa04fe273aa6 100755 --- a/.github/scripts/shellcheck-ci.sh +++ b/.github/scripts/shellcheck-ci.sh @@ -18,7 +18,7 @@ git ls-files '.github/actions/**/*.yml' '.github/workflows/*.yml' | while read - echo '#!/usr/bin/env bash' echo 'set -eu' yq eval '.. | select(has("run") and (.run | type == "!!str")) | .run + "\ntrue\n"' "${f}" - } | sed -E 's|\$\{\{ .+ \}\}|GHA_EXPRESSION|g' | shellcheck - + } | shellcheck - done # Circle CI From 43029e2ec37eba2354ced199ccd8898de695858e Mon Sep 17 00:00:00 2001 From: Stefan Eissing Date: Wed, 9 Sep 2026 10:37:26 +0200 Subject: [PATCH 082/481] url: ntlm/negotiate, prohibit connection reuse on follows When a transfer looks for a connection other than its initial origin, do not match NTLM/Negotiate using connections. Closes #22884 --- lib/url.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/url.c b/lib/url.c index d848eba09d4f..9ce95cccd53f 100644 --- a/lib/url.c +++ b/lib/url.c @@ -1139,7 +1139,8 @@ static bool url_attach_existing(struct Curl_easy *data, #ifdef USE_NTLM match.want_ntlm_http = (data->state.authhost.want & CURLAUTH_NTLM) && - (needle->scheme->protocol & PROTO_FAMILY_HTTP); + (needle->scheme->protocol & PROTO_FAMILY_HTTP) && + Curl_auth_allowed_to_host(data); #ifndef CURL_DISABLE_PROXY match.want_proxy_ntlm_http = needle->http_proxy.creds && @@ -1151,7 +1152,8 @@ static bool url_attach_existing(struct Curl_easy *data, #if !defined(CURL_DISABLE_HTTP) && defined(USE_SPNEGO) match.want_nego_http = (data->state.authhost.want & CURLAUTH_NEGOTIATE) && - (needle->scheme->protocol & PROTO_FAMILY_HTTP); + (needle->scheme->protocol & PROTO_FAMILY_HTTP) && + Curl_auth_allowed_to_host(data); #ifndef CURL_DISABLE_PROXY match.want_proxy_nego_http = needle->http_proxy.creds && From 66aaba7fb33f8dd9867d911b5a24ad3c11239b01 Mon Sep 17 00:00:00 2001 From: Stefan Eissing Date: Wed, 9 Sep 2026 11:53:58 +0200 Subject: [PATCH 083/481] hash: change key arguments to const hash.h functions had `void *key` as argument in several functions and those should be `const void *key` instead as they are not modified. Saves a ton of `CURL_UNCONST` use, often on string constants where they are definitely not wanted. Closes #22886 --- lib/conncache.c | 11 ++++------- lib/easy.c | 8 ++++---- lib/file.c | 2 +- lib/ftp.c | 4 ++-- lib/hash.c | 33 ++++++++++++++++++--------------- lib/hash.h | 14 ++++++++------ lib/imap.c | 4 ++-- lib/mqtt.c | 4 ++-- lib/multi_ev.c | 4 ++-- lib/openldap.c | 4 ++-- lib/pop3.c | 4 ++-- lib/rtsp.c | 4 ++-- lib/smb.c | 4 ++-- lib/smtp.c | 4 ++-- lib/telnet.c | 2 +- lib/tftp.c | 2 +- lib/url.c | 8 ++++---- lib/url.h | 2 +- lib/vauth/vauth.c | 8 ++++---- lib/vdns/doh.c | 2 +- lib/vssh/libssh.c | 4 ++-- lib/vssh/libssh2.c | 4 ++-- lib/vtls/gtls.c | 7 ++++--- lib/vtls/openssl.c | 8 ++++---- lib/vtls/schannel.c | 8 ++++---- lib/vtls/wolfssl.c | 8 ++++---- lib/ws.c | 2 +- tests/unit/unit1603.c | 2 +- 28 files changed, 87 insertions(+), 84 deletions(-) diff --git a/lib/conncache.c b/lib/conncache.c index bd94d9d5439b..bbd65bf4813e 100644 --- a/lib/conncache.c +++ b/lib/conncache.c @@ -136,7 +136,7 @@ static struct cpool_bundle *cpool_find_bundle(struct cpool *cpool, const char *destination) { return Curl_hash_pick( - &cpool->dest2bundle, CURL_UNCONST(destination), strlen(destination) + 1); + &cpool->dest2bundle, destination, strlen(destination) + 1); } static void cpool_remove_bundle(struct cpool *cpool, @@ -144,8 +144,7 @@ static void cpool_remove_bundle(struct cpool *cpool, { if(!cpool) return; - Curl_hash_delete(&cpool->dest2bundle, - CURL_UNCONST(destination), strlen(destination) + 1); + Curl_hash_delete(&cpool->dest2bundle, destination, strlen(destination) + 1); } static void cpool_remove_conn(struct cpool *cpool, @@ -303,8 +302,7 @@ static struct cpool_bundle *cpool_add_bundle(struct cpool *cpool, return NULL; if(!Curl_hash_add(&cpool->dest2bundle, - CURL_UNCONST(destination), strlen(destination) + 1, - bundle)) { + destination, strlen(destination) + 1, bundle)) { cpool_bundle_destroy(bundle); return NULL; } @@ -712,8 +710,7 @@ bool Curl_cpool_find(struct Curl_easy *data, CPOOL_LOCK(cpool, data); bundle = Curl_hash_pick(&cpool->dest2bundle, - CURL_UNCONST(destination), - strlen(destination) + 1); + destination, strlen(destination) + 1); if(bundle) { struct Curl_llist_node *curr = Curl_llist_head(&bundle->conns); while(curr) { diff --git a/lib/easy.c b/lib/easy.c index 49a461ac94fe..7264d3d4bd0a 100644 --- a/lib/easy.c +++ b/lib/easy.c @@ -1447,9 +1447,9 @@ CURLcode Curl_meta_set(struct Curl_easy *data, const char *key, void *meta_data, Curl_meta_dtor *meta_dtor) { DEBUGASSERT(meta_data); /* never set to NULL */ - if(!Curl_hash_add2(&data->meta_hash, CURL_UNCONST(key), strlen(key) + 1, + if(!Curl_hash_add2(&data->meta_hash, key, strlen(key) + 1, meta_data, meta_dtor)) { - meta_dtor(CURL_UNCONST(key), strlen(key) + 1, meta_data); + meta_dtor(key, strlen(key) + 1, meta_data); return CURLE_OUT_OF_MEMORY; } return CURLE_OK; @@ -1457,12 +1457,12 @@ CURLcode Curl_meta_set(struct Curl_easy *data, const char *key, void Curl_meta_remove(struct Curl_easy *data, const char *key) { - Curl_hash_delete(&data->meta_hash, CURL_UNCONST(key), strlen(key) + 1); + Curl_hash_delete(&data->meta_hash, key, strlen(key) + 1); } void *Curl_meta_get(struct Curl_easy *data, const char *key) { - return Curl_hash_pick(&data->meta_hash, CURL_UNCONST(key), strlen(key) + 1); + return Curl_hash_pick(&data->meta_hash, key, strlen(key) + 1); } void Curl_meta_reset(struct Curl_easy *data) diff --git a/lib/file.c b/lib/file.c index e674a16923a1..fd2b703ff585 100644 --- a/lib/file.c +++ b/lib/file.c @@ -90,7 +90,7 @@ static void file_cleanup(struct FILEPROTO *file) } } -static void file_easy_dtor(void *key, size_t klen, void *entry) +static void file_easy_dtor(const void *key, size_t klen, void *entry) { struct FILEPROTO *file = entry; (void)key; diff --git a/lib/ftp.c b/lib/ftp.c index 6a952a82690d..94b29c9a247d 100644 --- a/lib/ftp.c +++ b/lib/ftp.c @@ -4369,7 +4369,7 @@ static CURLcode ftp_doing(struct Curl_easy *data, return result; } -static void ftp_easy_dtor(void *key, size_t klen, void *entry) +static void ftp_easy_dtor(const void *key, size_t klen, void *entry) { struct FTP *ftp = entry; (void)key; @@ -4378,7 +4378,7 @@ static void ftp_easy_dtor(void *key, size_t klen, void *entry) curlx_free(ftp); } -static void ftp_conn_dtor(void *key, size_t klen, void *entry) +static void ftp_conn_dtor(const void *key, size_t klen, void *entry) { struct ftp_conn *ftpc = entry; (void)key; diff --git a/lib/hash.c b/lib/hash.c index 1c6cb0f60fa5..23883105caca 100644 --- a/lib/hash.c +++ b/lib/hash.c @@ -33,13 +33,13 @@ #define ITERINIT 0x5FEDCBA9 #endif -typedef size_t (*hash_function)(void *key, +typedef size_t (*hash_function)(const void *key, size_t key_length, size_t slots_num); -typedef size_t (*comp_function)(void *key1, +typedef size_t (*comp_function)(const void *key1, size_t key1_len, - void *key2, + const void *key2, size_t key2_len); /* Curl_hash stores its type in a uint8_t. */ @@ -57,7 +57,8 @@ static char *hash_elem_key(struct Curl_hash_element *he) return (char *)he + offsetof(struct Curl_hash_element, key); } -static size_t hash_socket(void *key, size_t key_length, size_t slots_num) +static size_t hash_socket(const void *key, size_t key_length, + size_t slots_num) { curl_socket_t socket; @@ -68,8 +69,8 @@ static size_t hash_socket(void *key, size_t key_length, size_t slots_num) return (size_t)(socket % (curl_socket_t)slots_num); } -static size_t compare_socket(void *key1, size_t key1_len, - void *key2, size_t key2_len) +static size_t compare_socket(const void *key1, size_t key1_len, + const void *key2, size_t key2_len) { curl_socket_t socket1; curl_socket_t socket2; @@ -81,8 +82,8 @@ static size_t compare_socket(void *key1, size_t key1_len, return socket1 == socket2; } -static size_t compare_bytes(void *key1, size_t key1_len, - void *key2, size_t key2_len) +static size_t compare_bytes(const void *key1, size_t key1_len, + const void *key2, size_t key2_len) { return (key1_len == key2_len) && !memcmp(key1, key2, key1_len); } @@ -171,7 +172,7 @@ void Curl_hash_init(struct Curl_hash *h, static struct Curl_hash_element *hash_elem_create(const void *key, size_t key_len, - const void *p, + void *p, Curl_hash_elem_dtor dtor) { struct Curl_hash_element *he; @@ -183,7 +184,7 @@ static struct Curl_hash_element *hash_elem_create(const void *key, /* copy the key */ memcpy(hash_elem_key(he), key, key_len); he->key_len = key_len; - he->ptr = CURL_UNCONST(p); + he->ptr = p; he->dtor = dtor; } return he; @@ -227,7 +228,8 @@ static void hash_elem_link(struct Curl_hash *h, ++h->size; } -void *Curl_hash_add2(struct Curl_hash *h, void *key, size_t key_len, void *p, +void *Curl_hash_add2(struct Curl_hash *h, + const void *key, size_t key_len, void *p, Curl_hash_elem_dtor dtor) { const struct hash_functions *functions; @@ -272,7 +274,8 @@ void *Curl_hash_add2(struct Curl_hash *h, void *key, size_t key_len, void *p, * @unittest: 1602 * @unittest: 1603 */ -void *Curl_hash_add(struct Curl_hash *h, void *key, size_t key_len, void *p) +void *Curl_hash_add(struct Curl_hash *h, + const void *key, size_t key_len, void *p) { return Curl_hash_add2(h, key, key_len, p, NULL); } @@ -282,7 +285,7 @@ void *Curl_hash_add(struct Curl_hash *h, void *key, size_t key_len, void *p) * * @unittest: 1603 */ -int Curl_hash_delete(struct Curl_hash *h, void *key, size_t key_len) +int Curl_hash_delete(struct Curl_hash *h, const void *key, size_t key_len) { const struct hash_functions *functions; @@ -313,7 +316,7 @@ int Curl_hash_delete(struct Curl_hash *h, void *key, size_t key_len) * * @unittest: 1603 */ -void *Curl_hash_pick(struct Curl_hash *h, void *key, size_t key_len) +void *Curl_hash_pick(struct Curl_hash *h, const void *key, size_t key_len) { const struct hash_functions *functions; @@ -406,7 +409,7 @@ void Curl_hash_clean_with_criterium(struct Curl_hash *h, void *user, } } -size_t Curl_hash_str(void *key, size_t key_length, size_t slots_num) +size_t Curl_hash_str(const void *key, size_t key_length, size_t slots_num) { const char *key_str = (const char *)key; const char *end = key_str + key_length; diff --git a/lib/hash.h b/lib/hash.h index 12719dd92ff6..02466d827e29 100644 --- a/lib/hash.h +++ b/lib/hash.h @@ -27,7 +27,7 @@ typedef void (*Curl_hash_dtor)(void *); -typedef void (*Curl_hash_elem_dtor)(void *key, size_t key_len, void *p); +typedef void (*Curl_hash_elem_dtor)(const void *key, size_t key_len, void *p); /* How keys in a hash are interpreted. */ typedef enum { @@ -70,18 +70,20 @@ void Curl_hash_init(struct Curl_hash *h, Curl_hash_type type, Curl_hash_dtor dtor); -void *Curl_hash_add(struct Curl_hash *h, void *key, size_t key_len, void *p); -void *Curl_hash_add2(struct Curl_hash *h, void *key, size_t key_len, void *p, +void *Curl_hash_add(struct Curl_hash *h, + const void *key, size_t key_len, void *p); +void *Curl_hash_add2(struct Curl_hash *h, + const void *key, size_t key_len, void *p, Curl_hash_elem_dtor dtor); -int Curl_hash_delete(struct Curl_hash *h, void *key, size_t key_len); -void *Curl_hash_pick(struct Curl_hash *h, void *key, size_t key_len); +int Curl_hash_delete(struct Curl_hash *h, const void *key, size_t key_len); +void *Curl_hash_pick(struct Curl_hash *h, const void *key, size_t key_len); void Curl_hash_destroy(struct Curl_hash *h); size_t Curl_hash_count(struct Curl_hash *h); void Curl_hash_clean(struct Curl_hash *h); void Curl_hash_clean_with_criterium(struct Curl_hash *h, void *user, int (*comp)(void *, void *)); -size_t Curl_hash_str(void *key, size_t key_length, size_t slots_num); +size_t Curl_hash_str(const void *key, size_t key_length, size_t slots_num); void Curl_hash_start_iterate(struct Curl_hash *hash, struct Curl_hash_iterator *iter); struct Curl_hash_element *Curl_hash_next_element( diff --git a/lib/imap.c b/lib/imap.c index c9037e6662f8..f06a20daf607 100644 --- a/lib/imap.c +++ b/lib/imap.c @@ -2237,7 +2237,7 @@ static CURLcode imap_doing(struct Curl_easy *data, bool *dophase_done) return result; } -static void imap_easy_dtor(void *key, size_t klen, void *entry) +static void imap_easy_dtor(const void *key, size_t klen, void *entry) { struct IMAP *imap = entry; (void)key; @@ -2246,7 +2246,7 @@ static void imap_easy_dtor(void *key, size_t klen, void *entry) curlx_free(imap); } -static void imap_conn_dtor(void *key, size_t klen, void *entry) +static void imap_conn_dtor(const void *key, size_t klen, void *entry) { struct imap_conn *imapc = entry; (void)key; diff --git a/lib/mqtt.c b/lib/mqtt.c index 29e92ce5c133..f493ca3d0dfd 100644 --- a/lib/mqtt.c +++ b/lib/mqtt.c @@ -91,7 +91,7 @@ struct MQTT { BIT(pingsent); /* 1 while we wait for ping response */ }; -static void mqtt_easy_dtor(void *key, size_t klen, void *entry) +static void mqtt_easy_dtor(const void *key, size_t klen, void *entry) { struct MQTT *mq = entry; (void)key; @@ -101,7 +101,7 @@ static void mqtt_easy_dtor(void *key, size_t klen, void *entry) curlx_free(mq); } -static void mqtt_conn_dtor(void *key, size_t klen, void *entry) +static void mqtt_conn_dtor(const void *key, size_t klen, void *entry) { (void)key; (void)klen; diff --git a/lib/multi_ev.c b/lib/multi_ev.c index ad489bbd859a..a5636da9c1f9 100644 --- a/lib/multi_ev.c +++ b/lib/multi_ev.c @@ -73,7 +73,7 @@ static struct mev_sh_entry *mev_sh_entry_get(struct Curl_hash *sh, { if(s != CURL_SOCKET_BAD) { /* only look for proper sockets */ - return Curl_hash_pick(sh, (char *)&s, sizeof(curl_socket_t)); + return Curl_hash_pick(sh, (const char *)&s, sizeof(curl_socket_t)); } return NULL; } @@ -433,7 +433,7 @@ static CURLMcode mev_pollset_diff(struct Curl_multi *multi, return CURLM_OK; } -static void mev_pollset_dtor(void *key, size_t klen, void *entry) +static void mev_pollset_dtor(const void *key, size_t klen, void *entry) { struct easy_pollset *ps = entry; (void)key; diff --git a/lib/openldap.c b/lib/openldap.c index 887336e5ca7a..03f9c2f1fa9e 100644 --- a/lib/openldap.c +++ b/lib/openldap.c @@ -554,7 +554,7 @@ static CURLcode oldap_perform_starttls(struct Curl_easy *data) } #endif -static void oldap_easy_dtor(void *key, size_t klen, void *entry) +static void oldap_easy_dtor(const void *key, size_t klen, void *entry) { struct ldapreqinfo *lr = entry; (void)key; @@ -562,7 +562,7 @@ static void oldap_easy_dtor(void *key, size_t klen, void *entry) curlx_free(lr); } -static void oldap_conn_dtor(void *key, size_t klen, void *entry) +static void oldap_conn_dtor(const void *key, size_t klen, void *entry) { struct ldapconninfo *li = entry; (void)key; diff --git a/lib/pop3.c b/lib/pop3.c index 16b742cc6c54..7ec3d2520e65 100644 --- a/lib/pop3.c +++ b/lib/pop3.c @@ -1650,7 +1650,7 @@ static CURLcode pop3_doing(struct Curl_easy *data, bool *dophase_done) return result; } -static void pop3_easy_dtor(void *key, size_t klen, void *entry) +static void pop3_easy_dtor(const void *key, size_t klen, void *entry) { struct POP3 *pop3 = entry; (void)key; @@ -1662,7 +1662,7 @@ static void pop3_easy_dtor(void *key, size_t klen, void *entry) curlx_free(pop3); } -static void pop3_conn_dtor(void *key, size_t klen, void *entry) +static void pop3_conn_dtor(const void *key, size_t klen, void *entry) { struct pop3_conn *pop3c = entry; (void)key; diff --git a/lib/rtsp.c b/lib/rtsp.c index 736f8aeabc1b..80ed636147dd 100644 --- a/lib/rtsp.c +++ b/lib/rtsp.c @@ -85,7 +85,7 @@ static CURLcode rtsp_do_pollset(struct Curl_easy *data, #define MAX_RTP_BUFFERSIZE 1000000 /* arbitrary */ -static void rtsp_easy_dtor(void *key, size_t klen, void *entry) +static void rtsp_easy_dtor(const void *key, size_t klen, void *entry) { struct RTSP *rtsp = entry; (void)key; @@ -93,7 +93,7 @@ static void rtsp_easy_dtor(void *key, size_t klen, void *entry) curlx_free(rtsp); } -static void rtsp_conn_dtor(void *key, size_t klen, void *entry) +static void rtsp_conn_dtor(const void *key, size_t klen, void *entry) { struct rtsp_conn *rtspc = entry; (void)key; diff --git a/lib/smb.c b/lib/smb.c index 4299190ff577..b82a99bdb10b 100644 --- a/lib/smb.c +++ b/lib/smb.c @@ -371,7 +371,7 @@ static void request_state(struct Curl_easy *data, } } -static void smb_easy_dtor(void *key, size_t klen, void *entry) +static void smb_easy_dtor(const void *key, size_t klen, void *entry) { struct smb_request *req = entry; (void)key; @@ -380,7 +380,7 @@ static void smb_easy_dtor(void *key, size_t klen, void *entry) curlx_free(req); } -static void smb_conn_dtor(void *key, size_t klen, void *entry) +static void smb_conn_dtor(const void *key, size_t klen, void *entry) { struct smb_conn *smbc = entry; (void)key; diff --git a/lib/smtp.c b/lib/smtp.c index 75fc5b7ece4b..b02eea23f9b5 100644 --- a/lib/smtp.c +++ b/lib/smtp.c @@ -1947,7 +1947,7 @@ static CURLcode smtp_doing(struct Curl_easy *data, bool *dophase_done) return result; } -static void smtp_easy_dtor(void *key, size_t klen, void *entry) +static void smtp_easy_dtor(const void *key, size_t klen, void *entry) { struct SMTP *smtp = entry; (void)key; @@ -1955,7 +1955,7 @@ static void smtp_easy_dtor(void *key, size_t klen, void *entry) curlx_free(smtp); } -static void smtp_conn_dtor(void *key, size_t klen, void *entry) +static void smtp_conn_dtor(const void *key, size_t klen, void *entry) { struct smtp_conn *smtpc = entry; (void)key; diff --git a/lib/telnet.c b/lib/telnet.c index 78afc5193d75..296b852c2f60 100644 --- a/lib/telnet.c +++ b/lib/telnet.c @@ -167,7 +167,7 @@ static void printoption(struct Curl_easy *data, } #endif /* !CURLVERBOSE */ -static void telnet_easy_dtor(void *key, size_t klen, void *entry) +static void telnet_easy_dtor(const void *key, size_t klen, void *entry) { struct TELNET *tn = entry; (void)key; diff --git a/lib/tftp.c b/lib/tftp.c index d95da69dc373..156aeb9e5595 100644 --- a/lib/tftp.c +++ b/lib/tftp.c @@ -883,7 +883,7 @@ static CURLcode tftp_state_machine(struct tftp_conn *state, return result; } -static void tftp_conn_dtor(void *key, size_t klen, void *entry) +static void tftp_conn_dtor(const void *key, size_t klen, void *entry) { struct tftp_conn *state = entry; (void)key; diff --git a/lib/url.c b/lib/url.c index 9ce95cccd53f..54bff4c4edb6 100644 --- a/lib/url.c +++ b/lib/url.c @@ -2549,9 +2549,9 @@ void Curl_data_priority_clear_state(struct Curl_easy *data) CURLcode Curl_conn_meta_set(struct connectdata *conn, const char *key, void *meta_data, Curl_meta_dtor *meta_dtor) { - if(!Curl_hash_add2(&conn->meta_hash, CURL_UNCONST(key), strlen(key) + 1, + if(!Curl_hash_add2(&conn->meta_hash, key, strlen(key) + 1, meta_data, meta_dtor)) { - meta_dtor(CURL_UNCONST(key), strlen(key) + 1, meta_data); + meta_dtor(key, strlen(key) + 1, meta_data); return CURLE_OUT_OF_MEMORY; } return CURLE_OK; @@ -2559,12 +2559,12 @@ CURLcode Curl_conn_meta_set(struct connectdata *conn, const char *key, void Curl_conn_meta_remove(struct connectdata *conn, const char *key) { - Curl_hash_delete(&conn->meta_hash, CURL_UNCONST(key), strlen(key) + 1); + Curl_hash_delete(&conn->meta_hash, key, strlen(key) + 1); } void *Curl_conn_meta_get(struct connectdata *conn, const char *key) { - return Curl_hash_pick(&conn->meta_hash, CURL_UNCONST(key), strlen(key) + 1); + return Curl_hash_pick(&conn->meta_hash, key, strlen(key) + 1); } struct Curl_easy *Curl_get_admin(struct Curl_easy *data) diff --git a/lib/url.h b/lib/url.h index 43ee63cfc597..cbb68795c934 100644 --- a/lib/url.h +++ b/lib/url.h @@ -48,7 +48,7 @@ CURLcode Curl_parse_login_details(const char *login, const size_t len, /* Attach/Clear/Get meta data for an easy handle. Needs to provide * a destructor, will be automatically called when the easy handle * is reset or closed. */ -typedef void Curl_meta_dtor(void *key, size_t key_len, void *meta_data); +typedef void Curl_meta_dtor(const void *key, size_t key_len, void *meta_data); /* Set the transfer meta data for the key. Any existing entry for that * key will be destroyed. diff --git a/lib/vauth/vauth.c b/lib/vauth/vauth.c index 3259556e5f78..d3849eb46c77 100644 --- a/lib/vauth/vauth.c +++ b/lib/vauth/vauth.c @@ -150,7 +150,7 @@ bool Curl_auth_allowed_to_origin(struct Curl_easy *data, } #ifdef USE_NTLM -static void ntlm_conn_dtor(void *key, size_t klen, void *entry) +static void ntlm_conn_dtor(const void *key, size_t klen, void *entry) { struct ntlmdata *ntlm = entry; (void)key; @@ -180,7 +180,7 @@ void Curl_auth_ntlm_remove(struct connectdata *conn, bool proxy) #endif /* USE_NTLM */ #ifdef USE_KERBEROS5 -static void krb5_conn_dtor(void *key, size_t klen, void *entry) +static void krb5_conn_dtor(const void *key, size_t klen, void *entry) { struct kerberos5data *krb5 = entry; (void)key; @@ -204,7 +204,7 @@ struct kerberos5data *Curl_auth_krb5_get(struct connectdata *conn) #endif /* USE_KERBEROS5 */ #ifdef USE_GSASL -static void gsasl_conn_dtor(void *key, size_t klen, void *entry) +static void gsasl_conn_dtor(const void *key, size_t klen, void *entry) { struct gsasldata *gsasl = entry; (void)key; @@ -228,7 +228,7 @@ struct gsasldata *Curl_auth_gsasl_get(struct connectdata *conn) #endif /* USE_GSASL */ #ifdef USE_SPNEGO -static void nego_conn_dtor(void *key, size_t klen, void *entry) +static void nego_conn_dtor(const void *key, size_t klen, void *entry) { struct negotiatedata *nego = entry; (void)key; diff --git a/lib/vdns/doh.c b/lib/vdns/doh.c index 007585f88ea8..04bc8d0ec20e 100644 --- a/lib/vdns/doh.c +++ b/lib/vdns/doh.c @@ -210,7 +210,7 @@ static size_t doh_probe_write_cb(char *contents, size_t size, size_t nmemb, static void doh_probe_done(struct Curl_easy *doh, struct Curl_easy *master, CURLcode result); -static void doh_probe_dtor(void *key, size_t klen, void *e) +static void doh_probe_dtor(const void *key, size_t klen, void *e) { (void)key; (void)klen; diff --git a/lib/vssh/libssh.c b/lib/vssh/libssh.c index 03a84d218b7a..5cc64b0396c7 100644 --- a/lib/vssh/libssh.c +++ b/lib/vssh/libssh.c @@ -2506,7 +2506,7 @@ static CURLcode myssh_block_statemach(struct Curl_easy *data, return result; } -static void myssh_easy_dtor(void *key, size_t klen, void *entry) +static void myssh_easy_dtor(const void *key, size_t klen, void *entry) { struct SSHPROTO *sshp = entry; (void)key; @@ -2515,7 +2515,7 @@ static void myssh_easy_dtor(void *key, size_t klen, void *entry) curlx_free(sshp); } -static void myssh_conn_dtor(void *key, size_t klen, void *entry) +static void myssh_conn_dtor(const void *key, size_t klen, void *entry) { struct ssh_conn *sshc = entry; (void)key; diff --git a/lib/vssh/libssh2.c b/lib/vssh/libssh2.c index f09db6156fb4..2630bdb1a21f 100644 --- a/lib/vssh/libssh2.c +++ b/lib/vssh/libssh2.c @@ -3236,7 +3236,7 @@ static CURLcode ssh_block_statemach(struct Curl_easy *data, return result; } -static void myssh_easy_dtor(void *key, size_t klen, void *entry) +static void myssh_easy_dtor(const void *key, size_t klen, void *entry) { struct SSHPROTO *sshp = entry; (void)key; @@ -3247,7 +3247,7 @@ static void myssh_easy_dtor(void *key, size_t klen, void *entry) curlx_free(sshp); } -static void myssh_conn_dtor(void *key, size_t klen, void *entry) +static void myssh_conn_dtor(const void *key, size_t klen, void *entry) { struct ssh_conn *sshc = entry; (void)key; diff --git a/lib/vtls/gtls.c b/lib/vtls/gtls.c index 915c514fa840..f298a24093ae 100644 --- a/lib/vtls/gtls.c +++ b/lib/vtls/gtls.c @@ -596,7 +596,7 @@ static struct gtls_shared_creds *gtls_get_cached_creds(struct Curl_cfilter *cf, if(data->multi) { shared_creds = Curl_hash_pick(&data->multi->proto_hash, - CURL_UNCONST(MPROTO_GTLS_X509_KEY), + MPROTO_GTLS_X509_KEY, CURL_CSTRLEN(MPROTO_GTLS_X509_KEY)); if(shared_creds && shared_creds->creds && !gtls_shared_creds_expired(data, shared_creds) && @@ -607,7 +607,8 @@ static struct gtls_shared_creds *gtls_get_cached_creds(struct Curl_cfilter *cf, return NULL; } -static void gtls_shared_creds_hash_free(void *key, size_t key_len, void *p) +static void gtls_shared_creds_hash_free(const void *key, + size_t key_len, void *p) { struct gtls_shared_creds *sc = p; DEBUGASSERT(key_len == CURL_CSTRLEN(MPROTO_GTLS_X509_KEY)); @@ -640,7 +641,7 @@ static void gtls_set_cached_creds(struct Curl_cfilter *cf, return; if(!Curl_hash_add2(&data->multi->proto_hash, - CURL_UNCONST(MPROTO_GTLS_X509_KEY), + MPROTO_GTLS_X509_KEY, CURL_CSTRLEN(MPROTO_GTLS_X509_KEY), sc, gtls_shared_creds_hash_free)) { Curl_gtls_shared_creds_free(&sc); /* down reference again */ diff --git a/lib/vtls/openssl.c b/lib/vtls/openssl.c index 8e4eae5f47ce..fcb39d061153 100644 --- a/lib/vtls/openssl.c +++ b/lib/vtls/openssl.c @@ -3159,7 +3159,7 @@ struct ossl_x509_share { BIT(no_partialchain); /* keep partial chain state */ }; -static void oss_x509_share_free(void *key, size_t key_len, void *p) +static void oss_x509_share_free(const void *key, size_t key_len, void *p) { struct ossl_x509_share *share = p; DEBUGASSERT(key_len == CURL_CSTRLEN(MPROTO_OSSL_X509_KEY)); @@ -3211,7 +3211,7 @@ static X509_STORE *ossl_get_cached_x509_store(struct Curl_cfilter *cf, DEBUGASSERT(multi); *pempty = TRUE; share = multi ? Curl_hash_pick(&multi->proto_hash, - CURL_UNCONST(MPROTO_OSSL_X509_KEY), + MPROTO_OSSL_X509_KEY, CURL_CSTRLEN(MPROTO_OSSL_X509_KEY)) : NULL; if(share && share->store && !ossl_cached_x509_store_expired(data, share) && @@ -3236,7 +3236,7 @@ static void ossl_set_cached_x509_store(struct Curl_cfilter *cf, if(!multi) return; share = Curl_hash_pick(&multi->proto_hash, - CURL_UNCONST(MPROTO_OSSL_X509_KEY), + MPROTO_OSSL_X509_KEY, CURL_CSTRLEN(MPROTO_OSSL_X509_KEY)); if(!share) { @@ -3244,7 +3244,7 @@ static void ossl_set_cached_x509_store(struct Curl_cfilter *cf, if(!share) return; if(!Curl_hash_add2(&multi->proto_hash, - CURL_UNCONST(MPROTO_OSSL_X509_KEY), + MPROTO_OSSL_X509_KEY, CURL_CSTRLEN(MPROTO_OSSL_X509_KEY), share, oss_x509_share_free)) { curlx_free(share); diff --git a/lib/vtls/schannel.c b/lib/vtls/schannel.c index b51793412355..251dd7a41749 100644 --- a/lib/vtls/schannel.c +++ b/lib/vtls/schannel.c @@ -2737,7 +2737,7 @@ HCERTSTORE Curl_schannel_get_cached_cert_store(struct Curl_cfilter *cf, } share = Curl_hash_pick(&multi->proto_hash, - CURL_UNCONST(MPROTO_SCHANNEL_CERT_SHARE_KEY), + MPROTO_SCHANNEL_CERT_SHARE_KEY, CURL_CSTRLEN(MPROTO_SCHANNEL_CERT_SHARE_KEY)); if(!share || !share->cert_store) { return NULL; @@ -2784,7 +2784,7 @@ HCERTSTORE Curl_schannel_get_cached_cert_store(struct Curl_cfilter *cf, return share->cert_store; } -static void schannel_cert_share_free(void *key, size_t key_len, void *p) +static void schannel_cert_share_free(const void *key, size_t key_len, void *p) { struct schannel_cert_share *share = p; DEBUGASSERT(key_len == CURL_CSTRLEN(MPROTO_SCHANNEL_CERT_SHARE_KEY)); @@ -2829,7 +2829,7 @@ bool Curl_schannel_set_cached_cert_store(struct Curl_cfilter *cf, } share = Curl_hash_pick(&multi->proto_hash, - CURL_UNCONST(MPROTO_SCHANNEL_CERT_SHARE_KEY), + MPROTO_SCHANNEL_CERT_SHARE_KEY, CURL_CSTRLEN(MPROTO_SCHANNEL_CERT_SHARE_KEY)); if(!share) { share = curlx_calloc(1, sizeof(*share)); @@ -2838,7 +2838,7 @@ bool Curl_schannel_set_cached_cert_store(struct Curl_cfilter *cf, return FALSE; } if(!Curl_hash_add2(&multi->proto_hash, - CURL_UNCONST(MPROTO_SCHANNEL_CERT_SHARE_KEY), + MPROTO_SCHANNEL_CERT_SHARE_KEY, CURL_CSTRLEN(MPROTO_SCHANNEL_CERT_SHARE_KEY), share, schannel_cert_share_free)) { curlx_free(share); diff --git a/lib/vtls/wolfssl.c b/lib/vtls/wolfssl.c index 2bf01df600a5..12e8f9702df7 100644 --- a/lib/vtls/wolfssl.c +++ b/lib/vtls/wolfssl.c @@ -680,7 +680,7 @@ struct wssl_x509_share { struct curltime time; /* when the cached store was created */ }; -static void wssl_x509_share_free(void *key, size_t key_len, void *p) +static void wssl_x509_share_free(const void *key, size_t key_len, void *p) { struct wssl_x509_share *share = p; DEBUGASSERT(key_len == CURL_CSTRLEN(MPROTO_WSSL_X509_KEY)); @@ -725,7 +725,7 @@ static WOLFSSL_X509_STORE *wssl_get_cached_x509_store(struct Curl_cfilter *cf, DEBUGASSERT(multi); share = multi ? Curl_hash_pick(&multi->proto_hash, - CURL_UNCONST(MPROTO_WSSL_X509_KEY), + MPROTO_WSSL_X509_KEY, CURL_CSTRLEN(MPROTO_WSSL_X509_KEY)) : NULL; if(share && share->store && !wssl_cached_x509_store_expired(data, share) && @@ -748,7 +748,7 @@ static void wssl_set_cached_x509_store(struct Curl_cfilter *cf, if(!multi) return; share = Curl_hash_pick(&multi->proto_hash, - CURL_UNCONST(MPROTO_WSSL_X509_KEY), + MPROTO_WSSL_X509_KEY, CURL_CSTRLEN(MPROTO_WSSL_X509_KEY)); if(!share) { @@ -756,7 +756,7 @@ static void wssl_set_cached_x509_store(struct Curl_cfilter *cf, if(!share) return; if(!Curl_hash_add2(&multi->proto_hash, - CURL_UNCONST(MPROTO_WSSL_X509_KEY), + MPROTO_WSSL_X509_KEY, CURL_CSTRLEN(MPROTO_WSSL_X509_KEY), share, wssl_x509_share_free)) { curlx_free(share); diff --git a/lib/ws.c b/lib/ws.c index a7653df6781b..51084b3669c0 100644 --- a/lib/ws.c +++ b/lib/ws.c @@ -1360,7 +1360,7 @@ CURLcode Curl_ws_request(struct Curl_easy *data, struct dynbuf *req) return result; } -static void ws_conn_dtor(void *key, size_t klen, void *entry) +static void ws_conn_dtor(const void *key, size_t klen, void *entry) { struct websocket *ws = entry; (void)key; diff --git a/tests/unit/unit1603.c b/tests/unit/unit1603.c index 8185fe097fec..7de26bc82f51 100644 --- a/tests/unit/unit1603.c +++ b/tests/unit/unit1603.c @@ -34,7 +34,7 @@ static void t1603_mydtor(void *p) static size_t elem_dtor_calls; -static void my_elem_dtor(void *key, size_t key_len, void *p) +static void my_elem_dtor(const void *key, size_t key_len, void *p) { (void)p; (void)key; From fa8bf15ec4480b6f81f51980906cb2dc29b173b5 Mon Sep 17 00:00:00 2001 From: Stefan Eissing Date: Wed, 9 Sep 2026 10:44:22 +0200 Subject: [PATCH 084/481] test440: raise alloc limit spurious fails in some Windows CI runs, raise a bit more Closes #22885 --- tests/data/test440 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/data/test440 b/tests/data/test440 index 53651889bc09..eaf3f518a61e 100644 --- a/tests/data/test440 +++ b/tests/data/test440 @@ -75,7 +75,7 @@ https://this.hsts.example./%TESTNUMBER 7 -Allocations: 172 +Allocations: 200 From 24c86d189a73ae2eb67448a616a90d393c826780 Mon Sep 17 00:00:00 2001 From: Patrick Monnerat Date: Wed, 9 Sep 2026 13:56:22 +0200 Subject: [PATCH 085/481] os400: document curl_formadd_CCSID multi-chunk peculiarity Closes #22887 --- projects/OS400/README.OS400 | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/projects/OS400/README.OS400 b/projects/OS400/README.OS400 index 71a78990f378..52a64789e0c1 100644 --- a/projects/OS400/README.OS400 +++ b/projects/OS400/README.OS400 @@ -167,8 +167,19 @@ entry containing one of the above option. This additional entry holds the CCSID in its value field, and the option field is meaningless. It is not possible to have a string pointer and its CCSID across a function parameter/array boundary. + A single call to this function describes a single field. To support +old-style file data in multiple chunks, it is however possible to list +data options more than once within a single call. Because each data +option may be associated with a length option, these tuples must be +separated from each other by a CURLFORM_CONTENTTYPE option that serves +as a delimiter. In consequence, associated data and length options may not +be separated by a CURLFORM_CONTENTTYPE option, whatever the data option +count is. In all cases, the new-style multi-chunk file definition (several +fields with the same name and filename) must be preferred. Please note that CURLFORM_PTRCONTENTS and CURLFORM_BUFFERPTR are considered unconvertible strings and thus are NOT followed by a CCSID. + The form API is now deprecated by the MIME API and the latter should +be preferred for new designs. _ curl_easy_getinfo_ccsid() The following options are followed by a 'char * *' and a CCSID. Unlike From c0c4971fceb41f312065b96be623d24dcc399892 Mon Sep 17 00:00:00 2001 From: Daniel Stenberg Date: Wed, 9 Sep 2026 14:09:04 +0200 Subject: [PATCH 086/481] RELEASE-NOTES: synced --- RELEASE-NOTES | 57 +++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 49 insertions(+), 8 deletions(-) diff --git a/RELEASE-NOTES b/RELEASE-NOTES index 35b65f52ed0f..eb103545993a 100644 --- a/RELEASE-NOTES +++ b/RELEASE-NOTES @@ -4,8 +4,8 @@ curl and libcurl 8.22.1 Command line options: 278 curl_easy_setopt() options: 312 Public functions in libcurl: 100 - Authors: 1524 - Contributors: 3795 + Authors: 1526 + Contributors: 3800 This release includes the following changes: @@ -14,11 +14,16 @@ This release includes the following changes: This release includes the following bugfixes: + o build: convert HAS_TIME_T_UNSIGNED to a compile-time check [65] o build: drop `HTTP_ONLY` option [23] o clang-tidy: merge nested `if()`s [39] + o config2setopts: always set the security options for SCP/SFTP [67] + o conncache: remove bundle dest [50] o connectdata: use less curltime [34] o cookie: ignore individual cookie errors when loading [6] + o cshutdown: make it smaller [60] o curl_addrinfo: drop redundant parentheses [41] + o curl_fopen: restore the uid and gid checks [70] o CURLOPT*ISSUERCERT*: not verified cryptographically [28] o CURLOPT_SHARE.md: remove bad explanation for cookie sharing [7] o digest: check peer equality for staleness [24] @@ -26,12 +31,21 @@ This release includes the following bugfixes: o digest: parse without storing 'value' in local buffer [20] o docs: add the missing .html to the url-encode book link [3] o doh: reject undersized HTTPS RDATA [11] + o easy: make ca_cache_timeout standalone [51] o escape: polish encoding and decoding for speed [37] o ftp: tighten use_ssl check for conn reuse [19] + o ftp: URL encode file names when wildcarding [61] o getparam: switch off CONNECT headers when using -J [4] o global_init: tidy up, simplify, fix issues [43] + o hash: change key arguments to const [14] + o hsts: cap max-age to two years [47] + o hsts: on load, only remove exact duplicates [56] o HTTP-CONNECT: do not react to 401 responses [31] + o http2: changes in error handling [53] o http: reject resumed uploads [16] + o imap: compare selected mailbox names case-sensitively [57] + o libssh2: match hashed known_hosts entries by host [30] + o mbedtls: fix ticket handling [52] o mprintf: stage output and emit it in runs [38] o multi: reduce struct size with small cleanups [25] o multi: remove redundant completion message list [32] @@ -40,13 +54,19 @@ This release includes the following bugfixes: o multihandle: some easy shrinks [33] o ngtcp2: shrink max sendbuf [13] o openssl/gtls: reject CRLfile with native CA store, matching rustls [27] + o openssl: use non-deprecated API for MD5/MD4 [69] + o os400: document curl_formadd_CCSID multi-chunk peculiarity [5] o ossl_verifyhost: remove assumption of null termination of ASN1_STRING. [26] o runtests: Added runnernums in order to have O(1) lookup [45] o schannel: auto_client_cert conn matching [17] o schannel: clear borrowed sslContext on close [15] + o ssl: dedup ssl_fsslctx [35] o test1399: improve reliability [9] + o test1679: unset `SSL_CERT_FILE`, use `TESTNUMBER` [58] o tool: (re)set SSL verification in --libcurl output [1] o uint-bset: skip unused slots when counting + o url: ntlm/negotiate, prohibit connection reuse on follows [18] + o urldata: remove conn->given [40] o vquic: fix preprocessor check for old Darwin versions [8] o vtls: fix Curl_cert_hostcheck when the pattern is not a C string [12] o warnless: dedupe Intel compiler warning suppression pragmas [42] @@ -72,12 +92,13 @@ Planned upcoming removals include: This release would not have looked like this without help, code, reports and advice from friends like these: - Artem Prilutskiy, Aviv Engelberg, Christian Hesse, Daniel Lang, - Daniel Stenberg, David Benjamin, Fredrik Blau, Huang Yiheng, Joshua Rogers, - Martin Dukek, Max Dymond, Mihai-Gabriel Marin, Ray Satiro, renovate[bot], - Ron Kuper, Roshan Ramani, Rudolf Polzer, Stanislav Fort, Stefan Eissing, - Un1q32, Viktor Szakats - (21 contributors) + Artem Prilutskiy, Aviv Engelberg, Bobbeh Rhino, Christian Hesse, + Christian Ullrich, Claude, Daniel Lang, Daniel Stenberg, David Benjamin, + Florent Castelli, Fredrik Blau, Huang Yiheng, Joshua Rogers, Martin Dukek, + Max Dymond, Mihai-Gabriel Marin, Patrick Monnerat, rawsun007, Ray Satiro, + renovate[bot], Ron Kuper, Roshan Ramani, Rudolf Polzer, Stanislav Fort, + Stefan Eissing, Tyler Yankee, Un1q32, Viktor Szakats + (28 contributors) References to bug reports and discussions on issues: @@ -85,6 +106,7 @@ References to bug reports and discussions on issues: [2] = https://curl.se/bug/?i=22799 [3] = https://curl.se/bug/?i=22811 [4] = https://curl.se/bug/?i=22838 + [5] = https://curl.se/bug/?i=22887 [6] = https://curl.se/bug/?i=22840 [7] = https://curl.se/bug/?i=22788 [8] = https://curl.se/bug/?i=22797 @@ -92,9 +114,11 @@ References to bug reports and discussions on issues: [11] = https://curl.se/bug/?i=22791 [12] = https://curl.se/bug/?i=22795 [13] = https://curl.se/bug/?i=22786 + [14] = https://curl.se/bug/?i=22886 [15] = https://curl.se/bug/?i=22720 [16] = https://curl.se/bug/?i=22703 [17] = https://curl.se/bug/?i=22827 + [18] = https://curl.se/bug/?i=22884 [19] = https://curl.se/bug/?i=22823 [20] = https://curl.se/bug/?i=22831 [21] = https://curl.se/bug/?i=22787 @@ -105,17 +129,34 @@ References to bug reports and discussions on issues: [26] = https://curl.se/bug/?i=22822 [27] = https://curl.se/bug/?i=22826 [28] = https://curl.se/bug/?i=22825 + [30] = https://curl.se/bug/?i=22874 [31] = https://curl.se/bug/?i=22817 [32] = https://curl.se/bug/?i=22805 [33] = https://curl.se/bug/?i=22801 [34] = https://curl.se/bug/?i=22815 + [35] = https://curl.se/bug/?i=22876 [36] = https://curl.se/bug/?i=22806 [37] = https://curl.se/bug/?i=22780 [38] = https://curl.se/bug/?i=22766 [39] = https://curl.se/bug/?i=22793 + [40] = https://curl.se/bug/?i=22875 [41] = https://curl.se/bug/?i=22810 [42] = https://curl.se/bug/?i=22796 [43] = https://curl.se/bug/?i=22746 [44] = https://curl.se/bug/?i=22804 [45] = https://curl.se/bug/?i=22753 [46] = https://curl.se/bug/?i=22784 + [47] = https://curl.se/bug/?i=22878 + [50] = https://curl.se/bug/?i=22873 + [51] = https://curl.se/bug/?i=22877 + [52] = https://curl.se/bug/?i=22871 + [53] = https://curl.se/bug/?i=22870 + [56] = https://curl.se/bug/?i=22869 + [57] = https://curl.se/bug/?i=22868 + [58] = https://curl.se/bug/?i=22866 + [60] = https://curl.se/bug/?i=22862 + [61] = https://curl.se/bug/?i=22860 + [65] = https://curl.se/bug/?i=22849 + [67] = https://curl.se/bug/?i=22839 + [69] = https://curl.se/bug/?i=22221 + [70] = https://curl.se/bug/?i=22843 From 7c11f1a1bd53e2cd983fe153e45991302d097647 Mon Sep 17 00:00:00 2001 From: Stefan Eissing Date: Mon, 7 Sep 2026 11:10:11 +0200 Subject: [PATCH 087/481] url: match connections without force reuse and candidates Changes to connection matching for a transfer: - When a transfer previously used a connection and that still exists in the connection bundle to search *and* that still matches, use exactly that one. This eliminates the `force_reuse` bit in match making. - Always apply all match conditions before selecting a match or candidate. - Store a connection for transfers that want NTLM/Negotiate as candidate in the matcher, to be used when no more, suitable connection is found. - NTML/Negotiate: check for creds match when `conn->creds_origin` is present. This should be the safe indicator that authenticaion is happening for the connection (as compared to requests) and credentials then need to match for it. Closes #22857 --- lib/conncache.c | 38 +++++++++++++++++++------ lib/url.c | 74 +++++++++++++++---------------------------------- 2 files changed, 52 insertions(+), 60 deletions(-) diff --git a/lib/conncache.c b/lib/conncache.c index bbd65bf4813e..16010bce8e42 100644 --- a/lib/conncache.c +++ b/lib/conncache.c @@ -701,6 +701,8 @@ bool Curl_cpool_find(struct Curl_easy *data, { struct cpool *cpool = cpool_get_instance(data); struct cpool_bundle *bundle; + struct Curl_llist_node *curr; + struct connectdata *conn; bool found = FALSE; DEBUGASSERT(cpool); @@ -712,15 +714,35 @@ bool Curl_cpool_find(struct Curl_easy *data, bundle = Curl_hash_pick(&cpool->dest2bundle, destination, strlen(destination) + 1); if(bundle) { - struct Curl_llist_node *curr = Curl_llist_head(&bundle->conns); - while(curr) { - struct connectdata *conn = Curl_node_elem(curr); - /* Get next node now. callback might discard current */ - curr = Curl_node_next(curr); + if(data->state.lastconnect_id >= 0) { + /* Try to find the previously used connection in this bundle + * and if it still matches, use that one. This assures that + * an authentication involving several requests is using + * the same connection again. */ + curr = Curl_llist_head(&bundle->conns); + while(curr) { + conn = Curl_node_elem(curr); + curr = Curl_node_next(curr); + if(data->state.lastconnect_id == conn->connection_id) { + if(conn_cb(conn, userdata)) + found = TRUE; + break; + } + } + } - if(conn_cb(conn, userdata)) { - found = TRUE; - break; + if(!found) { + curr = Curl_llist_head(&bundle->conns); + while(curr) { + conn = Curl_node_elem(curr); + curr = Curl_node_next(curr); + /* Already tried a matching connection above. No need to + * invoke callback again for this one. */ + if((data->state.lastconnect_id != conn->connection_id) && + conn_cb(conn, userdata)) { + found = TRUE; + break; + } } } } diff --git a/lib/url.c b/lib/url.c index 54bff4c4edb6..8f0bbbaf125c 100644 --- a/lib/url.c +++ b/lib/url.c @@ -593,7 +593,6 @@ struct url_conn_match { BIT(require_tls); /* Requires TLS use from a clear-text start, can only * reuse connections that have TLS. */ BIT(wait_pipe); - BIT(force_reuse); BIT(seen_pending_conn); BIT(seen_single_use_conn); BIT(seen_multiplex_conn); @@ -766,7 +765,8 @@ static bool url_match_proxy_use(struct connectdata *conn, #ifndef CURL_DISABLE_HTTP static bool url_match_http_multiplex(struct connectdata *conn, - struct url_conn_match *m) + struct url_conn_match *m, + bool *pwait_pipe) { if(m->may_multiplex && (m->data->state.http_neg.allowed & (CURL_HTTP_V2x | CURL_HTTP_V3x)) && @@ -775,7 +775,7 @@ static bool url_match_http_multiplex(struct connectdata *conn, if(m->data->set.pipewait) { infof(m->data, "Server upgrade does not support multiplex yet, wait"); m->found = NULL; - m->wait_pipe = TRUE; + *pwait_pipe = TRUE; return TRUE; /* stop searching, we want to wait */ } infof(m->data, "Server upgrade cannot be used"); @@ -817,8 +817,8 @@ static bool url_match_http_version(struct connectdata *conn, return TRUE; } #else -#define url_match_http_multiplex(c, m) ((void)(c), (void)(m), TRUE) -#define url_match_http_version(c, m) ((void)(c), (void)(m), TRUE) +#define url_match_http_multiplex(c, m, w) ((void)(c), (void)(m), TRUE) +#define url_match_http_version(c, m) ((void)(c), (void)(m), TRUE) #endif static bool url_match_proto_config(struct connectdata *conn, @@ -909,8 +909,9 @@ static bool url_match_auth_ntlm(struct connectdata *conn, } else if(m->want_ntlm_http) { /* Transfer wants NTLM, connection is not using it. - * Do not reuse when connection has credentials and they differ. */ - if(conn->creds && + * Do not reuse when connection credentials state is bound to an origin + * and it or creds differ. */ + if(conn->creds_origin && (!Curl_creds_same(conn->creds, m->data->state.creds) || !Curl_peer_equal(conn->creds_origin, m->data->state.origin))) return FALSE; @@ -929,24 +930,6 @@ static bool url_match_auth_ntlm(struct connectdata *conn, return FALSE; } #endif - if(m->want_ntlm_http || m->want_proxy_ntlm_http) { - /* Credentials are already checked, we may use this connection. - * With NTLM being weird as it is, we MUST use a - * connection where it has already been fully negotiated. - * If it has not, we keep on looking for a better one. */ - m->found = conn; - - if((m->want_ntlm_http && - (conn->http_ntlm_state != NTLMSTATE_NONE)) || - (m->want_proxy_ntlm_http && - (conn->proxy_ntlm_state != NTLMSTATE_NONE))) { - /* We must use this connection, no other */ - m->force_reuse = TRUE; - return TRUE; - } - /* Continue look up for a better connection */ - return FALSE; - } return TRUE; } #else @@ -967,8 +950,9 @@ static bool url_match_auth_nego(struct connectdata *conn, } else if(m->want_nego_http) { /* Transfer wants Negotiate, connection is not using it. - * Do not reuse when connection has credentials and they differ. */ - if(conn->creds && + * Do not reuse when connection credentials state is bound to an origin + * and it or creds differ. */ + if(conn->creds_origin && (!Curl_creds_same(conn->creds, m->data->state.creds) || !Curl_peer_equal(conn->creds_origin, m->data->state.origin))) return FALSE; @@ -987,21 +971,6 @@ static bool url_match_auth_nego(struct connectdata *conn, return FALSE; } #endif - if(m->want_nego_http || m->want_proxy_nego_http) { - /* Credentials are already checked, we may use this connection. We MUST - * use a connection where it has already been fully negotiated. If it has - * not, we keep on looking for a better one. */ - m->found = conn; - if((m->want_nego_http && - (conn->http_negotiate_state != GSS_AUTHNONE)) || - (m->want_proxy_nego_http && - (conn->proxy_negotiate_state != GSS_AUTHNONE))) { - /* We must use this connection, no other */ - m->force_reuse = TRUE; - return TRUE; - } - return FALSE; /* get another */ - } return TRUE; } #else @@ -1011,7 +980,7 @@ static bool url_match_auth_nego(struct connectdata *conn, static bool url_match_conn(struct connectdata *conn, void *userdata) { struct url_conn_match *m = userdata; - /* Check if `conn` can be used for transfer `m->data` */ + bool wait_pipe = FALSE; /* general connect config setting match? */ if(!url_match_connect_config(conn, m)) @@ -1035,11 +1004,8 @@ static bool url_match_conn(struct connectdata *conn, void *userdata) if(!url_match_ssl_config(conn, m)) return FALSE; - if(!url_match_http_multiplex(conn, m)) + if(!url_match_http_multiplex(conn, m, &wait_pipe)) return FALSE; - else if(m->wait_pipe) - /* wait on multiplexing */ - return TRUE; if(!url_match_auth(conn, m)) return FALSE; @@ -1049,17 +1015,22 @@ static bool url_match_conn(struct connectdata *conn, void *userdata) if(!url_match_auth_ntlm(conn, m)) return FALSE; - else if(m->force_reuse) - return TRUE; if(!url_match_auth_nego(conn, m)) return FALSE; - else if(m->force_reuse) - return TRUE; if(!url_match_multiplex_limits(conn, m)) return FALSE; + /* The connection matches all conditions, but do we want to use it? */ + if(wait_pipe) { + /* The connection fits, but it's multiplex state has not been determined + * yet. Put the transfer into PENDING and wait for conn state change. */ + DEBUGASSERT(!m->found); + m->wait_pipe = TRUE; + return TRUE; + } + if(m->data->set.conn_max_age_ms > 0) { timediff_t age_ms = curlx_ptimediff_ms(&m->now, &conn->created); if(age_ms > m->data->set.conn_max_age_ms) { @@ -1105,7 +1076,6 @@ static bool url_match_result(void *userdata) "Found pending candidate for reuse and CURLOPT_PIPEWAIT is set"); match->wait_pipe = TRUE; } - match->force_reuse = FALSE; return FALSE; } From fd8c409d6ab7367051d04646a31e976e3db0c6ed Mon Sep 17 00:00:00 2001 From: Stefan Eissing Date: Wed, 9 Sep 2026 09:50:02 +0200 Subject: [PATCH 088/481] h2 push: fix port check on pushed authority Whe the recent elimination of `conn->give` scheme, the change of port check was wrong when a forward proxy was involved. The real fix is to the the origin scheme, not the connecion scheme, now that we have it. Closes #22882 --- lib/http2.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/http2.c b/lib/http2.c index f4abd550ff86..792da468f877 100644 --- a/lib/http2.c +++ b/lib/http2.c @@ -1440,7 +1440,7 @@ static int on_header(nghttp2_session *session, const nghttp2_frame *frame, /* no memory */ return NGHTTP2_ERR_CALLBACK_FAILURE; if(!curl_strequal(check, (const char *)value) && - ((data->state.origin->port != cf->conn->origin->scheme->defport) || + ((data->state.origin->port != data->state.origin->scheme->defport) || !curl_strequal(data->state.origin->hostname, (const char *)value))) { /* This is push is not for the same authority that was asked for in * the URL. RFC 7540 section 8.2 says: "A client MUST treat a From 1143bc9ac6912743a0fef78d5ac81426b172976d Mon Sep 17 00:00:00 2001 From: Daniel Stenberg Date: Wed, 9 Sep 2026 15:56:00 +0200 Subject: [PATCH 089/481] netrc: survive comment-only .netrc file A .netrc file with only a comment and no newline caused an NULL dereference Verify with test 2429 Reported-by: Max Dymond Closes #22889 --- lib/netrc.c | 2 ++ tests/data/Makefile.am | 1 + tests/data/test2429 | 47 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 50 insertions(+) create mode 100644 tests/data/test2429 diff --git a/lib/netrc.c b/lib/netrc.c index d78529833dcf..7991c935487a 100644 --- a/lib/netrc.c +++ b/lib/netrc.c @@ -562,6 +562,8 @@ static NETRCcode netrc_scan_file(struct Curl_easy *data, } store->loaded = TRUE; } + if(!curlx_dyn_len(filebuf)) + return NETRC_NO_MATCH; return netrc_scan(data, curlx_dyn_ptr(filebuf), hostname, user, pcreds); } diff --git a/tests/data/Makefile.am b/tests/data/Makefile.am index 1cb738496dd1..35cb00b8412e 100644 --- a/tests/data/Makefile.am +++ b/tests/data/Makefile.am @@ -1967,6 +1967,7 @@ TESTCASES = \ test2412 \ test2413 \ test2414 \ + test2429 \ test2453 \ \ test2500 \ diff --git a/tests/data/test2429 b/tests/data/test2429 new file mode 100644 index 000000000000..1565758888b9 --- /dev/null +++ b/tests/data/test2429 @@ -0,0 +1,47 @@ + + + + +netrc + + + +# Server-side + + +HTTP/1.1 200 OK +Content-Length: 6 +Content-Type: text/html + +-foo- + + + +# Client-side + + +http + + +use netrc file with only comments + + +# only a comment line + + + +http://%HOSTIP:%HTTPPORT/%TESTNUMBER --netrc --netrc-file %LOGDIR/netrc + + + +# Verify data after the test has been "shot" + + +GET /%TESTNUMBER HTTP/1.1 +Host: %HOSTIP:%HTTPPORT +User-Agent: curl/%VERSION +Accept: */* + + + + From 6ee61c3fe1479a6fbe85238fa83873b87b87f747 Mon Sep 17 00:00:00 2001 From: Stefan Eissing Date: Wed, 9 Sep 2026 14:48:29 +0200 Subject: [PATCH 090/481] lib: connection shutdown, cleanup code Move all shutdown related functions to cshutdn.c and give them `Curl_cshutdn_*` prefix. Add connection age handling methods to conncache.h, so that other code does not need to mess with details of how connections keep time. Closes #22888 --- lib/cfilters.c | 58 +----------------- lib/cfilters.h | 8 --- lib/conncache.c | 31 ++++++++-- lib/conncache.h | 13 +++- lib/connect.c | 68 +-------------------- lib/connect.h | 21 ------- lib/cshutdn.c | 147 +++++++++++++++++++++++++++++++++++++++++----- lib/cshutdn.h | 47 +++++++++++---- lib/telnet.c | 8 +-- lib/transfer.c | 6 +- lib/url.c | 18 +++--- lib/vdns/hostip.c | 4 +- lib/vtls/vtls.c | 6 +- 13 files changed, 227 insertions(+), 208 deletions(-) diff --git a/lib/cfilters.c b/lib/cfilters.c index ab96268a9e08..39a6db10a03a 100644 --- a/lib/cfilters.c +++ b/lib/cfilters.c @@ -183,62 +183,6 @@ void Curl_conn_cf_discard_all(struct Curl_easy *data, Curl_conn_cf_discard_chain(&conn->cfilter[sockindex], data); } -CURLcode Curl_conn_shutdown(struct Curl_easy *data, - int8_t sockindex, bool *done) -{ - struct Curl_cfilter *cf; - CURLcode result = CURLE_OK; - timediff_t timeout_ms; - - DEBUGASSERT(data->conn); - - if(!CONN_SOCK_IDX_VALID(sockindex)) - return CURLE_BAD_FUNCTION_ARGUMENT; - - /* Get the first connected filter that is not shut down already. */ - cf = data->conn->cfilter[sockindex]; - while(cf && (!cf->connected || cf->shutdown)) - cf = cf->next; - - if(!cf) { - *done = TRUE; - return CURLE_OK; - } - - *done = FALSE; - if(!Curl_shutdown_started(data->conn, sockindex)) { - Curl_shutdown_start(data, sockindex, 0); - } - else { - timeout_ms = Curl_shutdown_timeleft(data, data->conn, sockindex); - if(timeout_ms < 0) { - /* info message, since this might be regarded as acceptable */ - infof(data, "shutdown timeout"); - return CURLE_OPERATION_TIMEDOUT; - } - } - - while(cf) { - if(!cf->shutdown) { - bool cfdone = FALSE; - result = cf->cft->do_shutdown(cf, data, &cfdone); - if(result) { - CURL_TRC_CF(data, cf, "shut down failed with %d", (int)result); - return result; - } - else if(!cfdone) { - CURL_TRC_CF(data, cf, "shut down not done yet"); - return CURLE_OK; - } - CURL_TRC_CF(data, cf, "shut down successfully"); - cf->shutdown = TRUE; - } - cf = cf->next; - } - *done = (!result); - return result; -} - CURLcode Curl_cf_recv(struct Curl_easy *data, int8_t sockindex, char *buf, size_t len, size_t *pnread) { @@ -776,7 +720,7 @@ CURLcode Curl_conn_adjust_pollset(struct Curl_easy *data, for(i = 0; (i < (int)CURL_ARRAYSIZE(conn->cfilter)) && !result; ++i) { if(conn->cfilter[i] && (want_io || !Curl_conn_is_connected(conn, i) || - Curl_shutdown_started(conn, i))) + Curl_cshutdn_has_started(conn, i))) result = Curl_conn_cf_adjust_pollset(conn->cfilter[i], data, ps); } return result; diff --git a/lib/cfilters.h b/lib/cfilters.h index 979275b72979..e9e343113e48 100644 --- a/lib/cfilters.h +++ b/lib/cfilters.h @@ -438,14 +438,6 @@ void Curl_conn_cntrl_report_stats(struct Curl_easy *data, void Curl_conn_remove_setup_filters(struct Curl_easy *data, int8_t sockindex); -/** - * Shutdown the connection at `sockindex` non-blocking, using timeout - * from `data->set.shutdowntimeout`, default DEFAULT_SHUTDOWN_TIMEOUT_MS. - * Return CURLE_OK and *done == FALSE if not finished. - */ -CURLcode Curl_conn_shutdown(struct Curl_easy *data, - int8_t sockindex, bool *done); - /** * Return if data is pending in some connection filter at chain * `sockindex` for connection `data->conn`. diff --git a/lib/conncache.c b/lib/conncache.c index 16010bce8e42..6c90b60491cf 100644 --- a/lib/conncache.c +++ b/lib/conncache.c @@ -209,11 +209,11 @@ static void cpool_discard_conn(struct cpool *cpool, done = TRUE; if(!done) { /* Attempt to shutdown the connection right away. */ - Curl_conn_shutdown_once(admin, conn, &done); + Curl_cshutdn_try_once(admin, conn, &done); } if(done || !data->multi) - Curl_conn_terminate(admin, conn, FALSE); + Curl_cshutdn_terminate(admin, conn, FALSE); else { struct Curl_multi *multi = data->multi; size_t max_shutdowns = multi->max_total_connections; @@ -417,7 +417,7 @@ static void cpool_conn_close(struct cpool *cpool, else { /* No multi available, terminate */ infof(data, "closing connection #%" FMT_OFF_T, conn->connection_id); - Curl_conn_terminate(admin, conn, !aborted); + Curl_cshutdn_terminate(admin, conn, !aborted); } if(do_lock) @@ -442,7 +442,7 @@ static void cpool_evict_conn(struct cpool *cpool, { if(cpool->share) { cpool_remove_conn(cpool, conn); - Curl_conn_terminate(admin, conn, TRUE); + Curl_cshutdn_terminate(admin, conn, TRUE); } else cpool_conn_close(cpool, admin, conn, FALSE); @@ -552,7 +552,8 @@ int Curl_cpool_check_limits(struct Curl_easy *data, } CURLcode Curl_cpool_add(struct Curl_easy *data, - struct connectdata *conn) + struct connectdata *conn, + const struct curltime *pnow) { CURLcode result = CURLE_OK; struct cpool_bundle *bundle = NULL; @@ -563,6 +564,10 @@ CURLcode Curl_cpool_add(struct Curl_easy *data, if(!cpool) return CURLE_FAILED_INIT; + conn->created = *pnow; + conn->shutdown.start_ms[FIRSTSOCKET] = + conn->shutdown.start_ms[SECONDARYSOCKET] = -1; + CPOOL_LOCK(cpool, data); bundle = cpool_find_bundle(cpool, conn->destination); if(!bundle) { @@ -1006,6 +1011,22 @@ bool Curl_cpool_conn_seems_healthy(struct connectdata *conn, return healthy; } +void Curl_cpool_conn_was_used(struct Curl_easy *data, + struct connectdata *conn, + const struct curltime *pnow) +{ + (void)data; + conn->lastupkeep_ms = curlx_ptimediff_ms(pnow, &conn->created); +} + +timediff_t Curl_cpool_conn_age_ms(struct Curl_easy *data, + struct connectdata *conn, + const struct curltime *pnow) +{ + (void)data; + return curlx_ptimediff_ms(pnow, &conn->created); +} + #if 0 /* Useful for debugging the connection pool */ void Curl_cpool_print(struct cpool *cpool) diff --git a/lib/conncache.h b/lib/conncache.h index 5e766c99d73e..a1d7973576b3 100644 --- a/lib/conncache.h +++ b/lib/conncache.h @@ -82,7 +82,18 @@ struct connectdata *Curl_cpool_get_conn(struct Curl_easy *data, /* Add the connection to the pool. */ CURLcode Curl_cpool_add(struct Curl_easy *data, - struct connectdata *conn) WARN_UNUSED_RESULT; + struct connectdata *conn, + const struct curltime *pnow) WARN_UNUSED_RESULT; + +/* Connection was used at the given time. */ +void Curl_cpool_conn_was_used(struct Curl_easy *data, + struct connectdata *conn, + const struct curltime *pnow); + +/* Return connection age in milliseconds since its creation. */ +timediff_t Curl_cpool_conn_age_ms(struct Curl_easy *data, + struct connectdata *conn, + const struct curltime *pnow); /** * Return if the pool has reached its configured limits for adding diff --git a/lib/connect.c b/lib/connect.c index 56bf46ae4691..8d5b8cb82723 100644 --- a/lib/connect.c +++ b/lib/connect.c @@ -73,8 +73,8 @@ UNITTEST timediff_t timeleft_now_ms(struct Curl_easy *data, timediff_t timeleft_ms = 0; timediff_t ctimeleft_ms = 0; - if(data->conn && Curl_shutdown_started(data->conn, FIRSTSOCKET)) - return Curl_shutdown_timeleft(data, data->conn, FIRSTSOCKET); + if(data->conn && Curl_cshutdn_has_started(data->conn, FIRSTSOCKET)) + return Curl_cshutdn_timeleft_ms(data, data->conn, FIRSTSOCKET); else if(Curl_is_connecting(data)) { timediff_t ctimeout_ms = (data->set.connecttimeout > 0) ? data->set.connecttimeout : DEFAULT_CONNECT_TIMEOUT; @@ -112,67 +112,6 @@ timediff_t Curl_timeleft_now_ms(struct Curl_easy *data, return timeleft_now_ms(data, pnow); } -void Curl_shutdown_start(struct Curl_easy *data, int8_t sockindex, - int timeout_ms) -{ - struct connectdata *conn = data->conn; - const struct curltime *pnow = Curl_pgrs_now(data); - - DEBUGASSERT(conn); - conn->shutdown.start_ms[sockindex] = - curlx_ptimediff_ms(pnow, &conn->created); - conn->shutdown.timeout_ms = (timeout_ms > 0) ? - (timediff_t)timeout_ms : - ((data->set.shutdowntimeout > 0) ? - data->set.shutdowntimeout : DEFAULT_SHUTDOWN_TIMEOUT_MS); - /* Set a timer, unless we operate on the admin handle */ - if(data->mid) - Curl_expire_set(data, EXPIRE_SHUTDOWN, conn->shutdown.timeout_ms, pnow); - CURL_TRC_M(data, "shutdown start on%s connection", - sockindex ? " secondary" : ""); -} - -timediff_t Curl_shutdown_timeleft(struct Curl_easy *data, - struct connectdata *conn, - int8_t sockindex) -{ - timediff_t left_ms; - - if(conn->shutdown.start_ms[sockindex] < 0) - return 0; /* not started or no limits */ - - left_ms = conn->shutdown.timeout_ms - - (curlx_ptimediff_ms(Curl_pgrs_now(data), &conn->created) - - conn->shutdown.start_ms[sockindex]); - return left_ms ? left_ms : -1; -} - -timediff_t Curl_conn_shutdown_timeleft(struct Curl_easy *data, - struct connectdata *conn) -{ - timediff_t left_ms = 0, ms; - int8_t i; - - for(i = 0; conn->shutdown.timeout_ms && (i < 2); ++i) { - if(conn->shutdown.start_ms[i] < 0) - continue; - ms = Curl_shutdown_timeleft(data, conn, i); - if(ms && (!left_ms || ms < left_ms)) - left_ms = ms; - } - return left_ms; -} - -void Curl_shutdown_clear(struct Curl_easy *data, int8_t sockindex) -{ - data->conn->shutdown.start_ms[sockindex] = -1; -} - -bool Curl_shutdown_started(struct connectdata *conn, int8_t sockindex) -{ - return conn->shutdown.start_ms[sockindex] >= 0; -} - /* * Used to extract socket and connectdata struct for the most recent * transfer on the given Curl_easy. @@ -377,8 +316,7 @@ CURLcode Curl_conn_connect(struct Curl_easy *data, * socket and ip related information. */ Curl_conn_cntrl_update_info(data, data->conn); conn_report_stats(data, sockindex); - data->conn->lastupkeep_ms = - curlx_ptimediff_ms(Curl_pgrs_now(data), &data->conn->created); + Curl_cpool_conn_was_used(data, data->conn, Curl_pgrs_now(data)); VERBOSE(result = conn_connect_trace(data, cf)); VERBOSE(Curl_conn_trc_filters(data, sockindex, "connected")); Curl_conn_remove_setup_filters(data, sockindex); diff --git a/lib/connect.h b/lib/connect.h index bc8c53aa83b5..54976fd62ed7 100644 --- a/lib/connect.h +++ b/lib/connect.h @@ -41,27 +41,6 @@ timediff_t Curl_timeleft_now_ms(struct Curl_easy *data, #define DEFAULT_CONNECT_TIMEOUT 300000 /* milliseconds == five minutes */ -#define DEFAULT_SHUTDOWN_TIMEOUT_MS (2 * 1000) - -void Curl_shutdown_start(struct Curl_easy *data, int8_t sockindex, - int timeout_ms); - -/* return how much time there is left to shutdown the connection at - * sockindex. Returns 0 if there is no limit or shutdown has not started. */ -timediff_t Curl_shutdown_timeleft(struct Curl_easy *data, - struct connectdata *conn, - int8_t sockindex); - -/* return how much time there is left to shutdown the connection. - * Returns 0 if there is no limit or shutdown has not started. */ -timediff_t Curl_conn_shutdown_timeleft(struct Curl_easy *data, - struct connectdata *conn); - -void Curl_shutdown_clear(struct Curl_easy *data, int8_t sockindex); - -/* TRUE iff shutdown has been started */ -bool Curl_shutdown_started(struct connectdata *conn, int8_t sockindex); - /* * Used to extract socket and connectdata struct for the most recent * transfer on the given Curl_easy. diff --git a/lib/cshutdn.c b/lib/cshutdn.c index 9fae2fab6812..cca2db874efd 100644 --- a/lib/cshutdn.c +++ b/lib/cshutdn.c @@ -38,6 +38,69 @@ #include "curlx/strparse.h" +#define DEFAULT_SHUTDOWN_TIMEOUT_MS (2 * 1000) + +void Curl_cshutdn_start_timer(struct Curl_easy *data, int8_t sockindex, + int timeout_ms) +{ + struct connectdata *conn = data->conn; + const struct curltime *pnow = Curl_pgrs_now(data); + + DEBUGASSERT(conn); + conn->shutdown.start_ms[sockindex] = + curlx_ptimediff_ms(pnow, &conn->created); + conn->shutdown.timeout_ms = (timeout_ms > 0) ? + (timediff_t)timeout_ms : + ((data->set.shutdowntimeout > 0) ? + data->set.shutdowntimeout : DEFAULT_SHUTDOWN_TIMEOUT_MS); + /* Set a timer, unless we operate on the admin handle */ + if(data->mid) + Curl_expire_set(data, EXPIRE_SHUTDOWN, conn->shutdown.timeout_ms, pnow); + CURL_TRC_M(data, "shutdown start on%s connection", + sockindex ? " secondary" : ""); +} + +timediff_t Curl_cshutdn_timeleft_ms(struct Curl_easy *data, + struct connectdata *conn, + int8_t sockindex) +{ + timediff_t left_ms; + + if(conn->shutdown.start_ms[sockindex] < 0) + return 0; /* not started or no limits */ + + left_ms = conn->shutdown.timeout_ms - + (curlx_ptimediff_ms(Curl_pgrs_now(data), &conn->created) - + conn->shutdown.start_ms[sockindex]); + return left_ms ? left_ms : -1; +} + +static timediff_t cshutdn_conn_timeleft(struct Curl_easy *data, + struct connectdata *conn) +{ + timediff_t left_ms = 0, ms; + int8_t i; + + for(i = 0; conn->shutdown.timeout_ms && (i < 2); ++i) { + if(conn->shutdown.start_ms[i] < 0) + continue; + ms = Curl_cshutdn_timeleft_ms(data, conn, i); + if(ms && (!left_ms || ms < left_ms)) + left_ms = ms; + } + return left_ms; +} + +void Curl_cshutdn_clear_timer(struct Curl_easy *data, int8_t sockindex) +{ + data->conn->shutdown.start_ms[sockindex] = -1; +} + +bool Curl_cshutdn_has_started(struct connectdata *conn, int8_t sockindex) +{ + return conn->shutdown.start_ms[sockindex] >= 0; +} + static void cshutdn_run_conn_handler(struct Curl_easy *data, struct connectdata *conn) { @@ -66,6 +129,62 @@ static void cshutdn_run_conn_handler(struct Curl_easy *data, } } +CURLcode Curl_cshutdn_try_once_idx(struct Curl_easy *data, + int8_t sockindex, bool *done) +{ + struct Curl_cfilter *cf; + CURLcode result = CURLE_OK; + timediff_t timeout_ms; + + DEBUGASSERT(data->conn); + + if(!CONN_SOCK_IDX_VALID(sockindex)) + return CURLE_BAD_FUNCTION_ARGUMENT; + + /* Get the first connected filter that is not shut down already. */ + cf = data->conn->cfilter[sockindex]; + while(cf && (!cf->connected || cf->shutdown)) + cf = cf->next; + + if(!cf) { + *done = TRUE; + return CURLE_OK; + } + + *done = FALSE; + if(!Curl_cshutdn_has_started(data->conn, sockindex)) { + Curl_cshutdn_start_timer(data, sockindex, 0); + } + else { + timeout_ms = Curl_cshutdn_timeleft_ms(data, data->conn, sockindex); + if(timeout_ms < 0) { + /* info message, since this might be regarded as acceptable */ + infof(data, "shutdown timeout"); + return CURLE_OPERATION_TIMEDOUT; + } + } + + while(cf) { + if(!cf->shutdown) { + bool cfdone = FALSE; + result = cf->cft->do_shutdown(cf, data, &cfdone); + if(result) { + CURL_TRC_CF(data, cf, "shut down failed with %d", (int)result); + return result; + } + else if(!cfdone) { + CURL_TRC_CF(data, cf, "shut down not done yet"); + return CURLE_OK; + } + CURL_TRC_CF(data, cf, "shut down successfully"); + cf->shutdown = TRUE; + } + cf = cf->next; + } + *done = (!result); + return result; +} + static void cshutdn_run_once(struct Curl_easy *data, struct connectdata *conn, bool *done) @@ -76,8 +195,8 @@ static void cshutdn_run_once(struct Curl_easy *data, /* We expect to be attached when called */ DEBUGASSERT(data->conn == conn); - if(!Curl_shutdown_started(conn, FIRSTSOCKET)) { - Curl_shutdown_start(data, FIRSTSOCKET, 0); + if(!Curl_cshutdn_has_started(conn, FIRSTSOCKET)) { + Curl_cshutdn_start_timer(data, FIRSTSOCKET, 0); } cshutdn_run_conn_handler(data, conn); @@ -88,14 +207,14 @@ static void cshutdn_run_once(struct Curl_easy *data, } if(!conn->bits.connect_only && Curl_conn_is_connected(conn, FIRSTSOCKET)) - r1 = Curl_conn_shutdown(data, FIRSTSOCKET, &done1); + r1 = Curl_cshutdn_try_once_idx(data, FIRSTSOCKET, &done1); else { r1 = CURLE_OK; done1 = TRUE; } if(!conn->bits.connect_only && Curl_conn_is_connected(conn, SECONDARYSOCKET)) - r2 = Curl_conn_shutdown(data, SECONDARYSOCKET, &done2); + r2 = Curl_cshutdn_try_once_idx(data, SECONDARYSOCKET, &done2); else { r2 = CURLE_OK; done2 = TRUE; @@ -107,7 +226,7 @@ static void cshutdn_run_once(struct Curl_easy *data, conn->bits.shutdown_filters = TRUE; } -void Curl_conn_shutdown_once(struct Curl_easy *admin, +void Curl_cshutdn_try_once(struct Curl_easy *admin, struct connectdata *conn, bool *done) { @@ -119,9 +238,9 @@ void Curl_conn_shutdown_once(struct Curl_easy *admin, Curl_detach_connection(admin); } -void Curl_conn_terminate(struct Curl_easy *admin, - struct connectdata *conn, - bool do_shutdown) +void Curl_cshutdn_terminate(struct Curl_easy *admin, + struct connectdata *conn, + bool do_shutdown) { bool done; @@ -179,7 +298,7 @@ static bool cshutdn_destroy_oldest(struct cshutdn *cshutdn, Curl_node_remove(e); sigpipe_init(&sigpipe_ctx); sigpipe_apply(admin, &sigpipe_ctx); - Curl_conn_terminate(admin, conn, FALSE); + Curl_cshutdn_terminate(admin, conn, FALSE); sigpipe_restore(&sigpipe_ctx); return TRUE; } @@ -238,15 +357,15 @@ static void cshutdn_perform(struct cshutdn *cshutdn, while(e) { enext = Curl_node_next(e); conn = Curl_node_elem(e); - Curl_conn_shutdown_once(admin, conn, &done); + Curl_cshutdn_try_once(admin, conn, &done); if(done) { Curl_node_remove(e); - Curl_conn_terminate(admin, conn, FALSE); + Curl_cshutdn_terminate(admin, conn, FALSE); } else { /* idata has one timer list, but maybe more than one connection. * Set EXPIRE_SHUTDOWN to the smallest time left for all. */ - ms = Curl_conn_shutdown_timeleft(admin, conn); + ms = cshutdn_conn_timeleft(admin, conn); if(ms && (!next_expire_ms || (ms < next_expire_ms))) next_expire_ms = ms; } @@ -299,7 +418,7 @@ static void cshutdn_terminate_all(struct cshutdn *cshutdn, while(e) { struct connectdata *conn = Curl_node_elem(e); Curl_node_remove(e); - Curl_conn_terminate(admin, conn, FALSE); + Curl_cshutdn_terminate(admin, conn, FALSE); e = Curl_llist_head(&cshutdn->list); } DEBUGASSERT(!Curl_llist_count(&cshutdn->list)); @@ -389,7 +508,7 @@ void Curl_cshutdn_add(struct cshutdn *cshutdn, if(multi->socket_cb && cshutdn_update_ev(multi, conn)) { CURL_TRC_M(admin, "[SHUTDOWN] update events failed, discarding #%" FMT_OFF_T, conn->connection_id); - Curl_conn_terminate(admin, conn, FALSE); + Curl_cshutdn_terminate(admin, conn, FALSE); return; } diff --git a/lib/cshutdn.h b/lib/cshutdn.h index b0da4f49b220..d6f7fb4215e0 100644 --- a/lib/cshutdn.h +++ b/lib/cshutdn.h @@ -32,21 +32,44 @@ struct Curl_multi; struct Curl_share; struct Curl_sigpipe_ctx; -/* Run the shutdown of the connection once. + +/* Terminates the connection, e.g. closes and destroys it. + * If `do_shutdown` is TRUE, the shutdown will be run once. + * Takes ownership of `conn`. `conn` MUST no longer be in + * a pool or on a shutdown list. */ +void Curl_cshutdn_terminate(struct Curl_easy *admin, + struct connectdata *conn, + bool do_shutdown); + +/* Start the shutdown timer, + * marks the connection sockindex as being shut down. */ +void Curl_cshutdn_start_timer(struct Curl_easy *data, int8_t sockindex, + int timeout_ms); +/* Clear the shutdown timer at sockindex again. */ +void Curl_cshutdn_clear_timer(struct Curl_easy *data, int8_t sockindex); + +/* return how much time there is left to shutdown the connection at + * sockindex. Returns 0 if there is no limit or shutdown has not started. */ +timediff_t Curl_cshutdn_timeleft_ms(struct Curl_easy *data, + struct connectdata *conn, + int8_t sockindex); + +/* TRUE iff shutdown at sockindex has been started */ +bool Curl_cshutdn_has_started(struct connectdata *conn, int8_t sockindex); + +/* Shutdown the connection at `sockindex` non-blocking. + * Will start the shutdown timer if not already set. + * Return CURLE_OK and *done == FALSE if not finished. */ +CURLcode Curl_cshutdn_try_once_idx(struct Curl_easy *data, + int8_t sockindex, bool *done); + +/* Run the shutdown of the connection once non-blocking for both + * socket indices. Will start the shutdown timer if not already set. * Shortly attach/detach the admin handle to `conn` while doing so. * `done` will be set TRUE if any error was encountered or if * the connection was shut down completely. */ -void Curl_conn_shutdown_once(struct Curl_easy *admin, - struct connectdata *conn, - bool *done); - -/* Terminates the connection, e.g. closes and destroys it. - * If `do_shutdown` is TRUE, the shutdown will be run once before - * terminating it. - * Takes ownership of `conn`. */ -void Curl_conn_terminate(struct Curl_easy *admin, - struct connectdata *conn, - bool do_shutdown); +void Curl_cshutdn_try_once(struct Curl_easy *admin, + struct connectdata *conn, bool *done); /* A `cshutdown` is always owned by a multi handle to maintain * the connections to be shut down. It registers timers and diff --git a/lib/telnet.c b/lib/telnet.c index 296b852c2f60..79e1627c834a 100644 --- a/lib/telnet.c +++ b/lib/telnet.c @@ -1414,9 +1414,7 @@ static CURLcode telnet_do(struct Curl_easy *data, bool *done) } } /* switch */ - if(data->set.timeout && - curlx_ptimediff_ms(Curl_pgrs_now(data), &conn->created) >= - data->set.timeout) { + if(Curl_timeleft_ms(data) < 0) { failf(data, "Time-out"); result = CURLE_OPERATION_TIMEDOUT; keepon = FALSE; @@ -1532,9 +1530,7 @@ static CURLcode telnet_do(struct Curl_easy *data, bool *done) break; } /* poll switch statement */ - if(data->set.timeout && - curlx_ptimediff_ms(Curl_pgrs_now(data), &conn->created) >= - data->set.timeout) { + if(Curl_timeleft_ms(data) < 0) { failf(data, "Time-out"); result = CURLE_OPERATION_TIMEDOUT; keepon = FALSE; diff --git a/lib/transfer.c b/lib/transfer.c index 3b6c5c03dd36..3c3ad04c2ec2 100644 --- a/lib/transfer.c +++ b/lib/transfer.c @@ -147,21 +147,21 @@ static CURLcode xfer_recv_shutdown(struct Curl_easy *data, bool *done) { if(!data || !data->conn) return CURLE_FAILED_INIT; - return Curl_conn_shutdown(data, data->conn->recv_idx, done); + return Curl_cshutdn_try_once_idx(data, data->conn->recv_idx, done); } static bool xfer_recv_shutdown_started(struct Curl_easy *data) { if(!data || !data->conn) return FALSE; - return Curl_shutdown_started(data->conn, data->conn->recv_idx); + return Curl_cshutdn_has_started(data->conn, data->conn->recv_idx); } CURLcode Curl_xfer_send_shutdown(struct Curl_easy *data, bool *done) { if(!data || !data->conn) return CURLE_FAILED_INIT; - return Curl_conn_shutdown(data, data->conn->send_idx, done); + return Curl_cshutdn_try_once_idx(data, data->conn->send_idx, done); } /** diff --git a/lib/url.c b/lib/url.c index 8f0bbbaf125c..5bb1c5207182 100644 --- a/lib/url.c +++ b/lib/url.c @@ -1032,7 +1032,7 @@ static bool url_match_conn(struct connectdata *conn, void *userdata) } if(m->data->set.conn_max_age_ms > 0) { - timediff_t age_ms = curlx_ptimediff_ms(&m->now, &conn->created); + timediff_t age_ms = Curl_cpool_conn_age_ms(m->data, conn, &m->now); if(age_ms > m->data->set.conn_max_age_ms) { /* Transfer is looking for a younger connection. */ if(!CONN_INUSE(conn)) @@ -1148,16 +1148,13 @@ static bool url_attach_existing(struct Curl_easy *data, /* * Allocate and initialize a new connectdata object. */ -static struct connectdata *allocate_conn(struct Curl_easy *data, - const struct curltime *pnow) +static struct connectdata *allocate_conn(struct Curl_easy *data) { struct connectdata *conn = curlx_calloc(1, sizeof(struct connectdata)); if(!conn) return NULL; /* and we setup a few fields in case we end up actually using this struct */ - - conn->created = *pnow; conn->sock[FIRSTSOCKET] = CURL_SOCKET_BAD; /* no file descriptor */ conn->sock[SECONDARYSOCKET] = CURL_SOCKET_BAD; /* no file descriptor */ conn->recv_idx = 0; /* default for receiving transfer data */ @@ -1972,7 +1969,6 @@ static void conn_meta_freeentry(void *p) } static CURLcode url_create_needle(struct Curl_easy *data, - const struct curltime *pnow, struct connectdata **pneedle) { struct connectdata *needle = NULL; @@ -1981,7 +1977,7 @@ static CURLcode url_create_needle(struct Curl_easy *data, /* Allocate a temporary connection data struct (needle) and fill in for comparison purposes. */ - needle = allocate_conn(data, pnow); + needle = allocate_conn(data); if(!needle) { result = CURLE_OUT_OF_MEMORY; goto out; @@ -2221,7 +2217,7 @@ static CURLcode url_find_or_create_conn(struct Curl_easy *data, /* create the template connection for transfer data. Use this needle to * find an existing connection or, if none exists, convert needle * to a full connection and attach it to data. */ - result = url_create_needle(data, pnow, &needle); + result = url_create_needle(data, &needle); if(result) goto out; DEBUGASSERT(needle); @@ -2243,7 +2239,7 @@ static CURLcode url_find_or_create_conn(struct Curl_easy *data, goto out; /* Setup a "faked" transfer that will do nothing */ - result = Curl_cpool_add(data, needle); + result = Curl_cpool_add(data, needle, pnow); Curl_attach_connection(data, needle, TRUE); needle = NULL; if(!result) { @@ -2320,7 +2316,7 @@ static CURLcode url_find_or_create_conn(struct Curl_easy *data, goto out; } else { - switch(Curl_cpool_check_limits(data, needle, &needle->created)) { + switch(Curl_cpool_check_limits(data, needle, pnow)) { case CPOOL_LIMIT_DEST: infof(data, "No more connections allowed to host"); result = CURLE_NO_CONNECTION_AVAILABLE; @@ -2351,7 +2347,7 @@ static CURLcode url_find_or_create_conn(struct Curl_easy *data, /* Add needle to conn pool, which assigns the connection id. * Attach regardless of result, for correct handling. */ - result = Curl_cpool_add(data, needle); + result = Curl_cpool_add(data, needle, pnow); Curl_attach_connection(data, needle, TRUE); needle = NULL; if(result) diff --git a/lib/vdns/hostip.c b/lib/vdns/hostip.c index 8464bd291773..ce893aeccfcb 100644 --- a/lib/vdns/hostip.c +++ b/lib/vdns/hostip.c @@ -950,8 +950,8 @@ static CURLcode resolv_alarm_timeout(struct Curl_easy *data, the time we spent until now! */ if(prev_alarm) { /* there was an alarm() set before us, now put it back */ - timediff_t elapsed_secs = curlx_ptimediff_ms(Curl_pgrs_now(data), - &data->conn->created) / 1000; + timediff_t elapsed_secs = + Curl_cpool_conn_age_ms(data, data->conn, Curl_pgrs_now(data)) / 1000; /* the alarm period is counted in even number of seconds */ unsigned long alarm_set = (unsigned long)(prev_alarm - elapsed_secs); diff --git a/lib/vtls/vtls.c b/lib/vtls/vtls.c index 8a90c7586af1..da739f0e458c 100644 --- a/lib/vtls/vtls.c +++ b/lib/vtls/vtls.c @@ -1520,7 +1520,7 @@ static CURLcode vtls_shutdown_blocking(struct Curl_cfilter *cf, *done = FALSE; while(!result && !*done && loop--) { - timeout_ms = Curl_shutdown_timeleft(data, cf->conn, cf->sockindex); + timeout_ms = Curl_cshutdn_timeleft_ms(data, cf->conn, cf->sockindex); if(timeout_ms < 0) { /* no need to continue if time is already up */ @@ -1567,9 +1567,9 @@ CURLcode Curl_ssl_cfilter_remove(struct Curl_easy *data, if(cf->cft == &Curl_cft_ssl) { bool done; CURL_TRC_CF(data, cf, "shutdown and remove SSL, start"); - Curl_shutdown_start(data, sockindex, 0); + Curl_cshutdn_start_timer(data, sockindex, 0); result = vtls_shutdown_blocking(cf, data, send_shutdown, &done); - Curl_shutdown_clear(data, sockindex); + Curl_cshutdn_clear_timer(data, sockindex); if(!result && !done) /* blocking failed? */ result = CURLE_SSL_SHUTDOWN_FAILED; Curl_conn_cf_discard(&cf, data); From 7d7fa4d7dd182b1bbb72ca20232fc15d00f6b335 Mon Sep 17 00:00:00 2001 From: Daniel Stenberg Date: Thu, 10 Sep 2026 08:53:25 +0200 Subject: [PATCH 091/481] easy: duplicating a zero byte memory area needs no malloc Added test 1791 to reproduce and verify fix. Reported-by: Max Dymond Closes #22893 --- lib/easy.c | 6 ++-- tests/data/Makefile.am | 2 +- tests/data/test1791 | 32 +++++++++++++++++++++ tests/libtest/Makefile.inc | 1 + tests/libtest/lib1791.c | 59 ++++++++++++++++++++++++++++++++++++++ 5 files changed, 96 insertions(+), 4 deletions(-) create mode 100644 tests/data/test1791 create mode 100644 tests/libtest/lib1791.c diff --git a/lib/easy.c b/lib/easy.c index 7264d3d4bd0a..2674c24712aa 100644 --- a/lib/easy.c +++ b/lib/easy.c @@ -957,10 +957,10 @@ static CURLcode dupset(struct Curl_easy *dst, struct Curl_easy *src) if(src->set.postfieldsize == -1) dst->set.str_copypostfields = curlx_strdup(src->set.str_copypostfields); else - /* postfieldsize is curl_off_t, curlx_memdup() takes a size_t ... */ + /* postfieldsize is curl_off_t, curlx_memdup0() takes a size_t ... */ dst->set.str_copypostfields = - curlx_memdup(src->set.str_copypostfields, - curlx_sotouz(src->set.postfieldsize)); + curlx_memdup0(src->set.str_copypostfields, + curlx_sotouz(src->set.postfieldsize)); if(!dst->set.str_copypostfields) return CURLE_OUT_OF_MEMORY; /* point to the new copy */ diff --git a/tests/data/Makefile.am b/tests/data/Makefile.am index 35cb00b8412e..f83ce4d022f3 100644 --- a/tests/data/Makefile.am +++ b/tests/data/Makefile.am @@ -1732,7 +1732,7 @@ TESTCASES = \ test1725 \ test1740 \ test1741 \ - \ + test1791 \ test1800 \ test1801 \ test1802 \ diff --git a/tests/data/test1791 b/tests/data/test1791 new file mode 100644 index 000000000000..1cbe039007bb --- /dev/null +++ b/tests/data/test1791 @@ -0,0 +1,32 @@ + + + + +CURLOPT_POSTFIELDSIZE_LARGE +duphandle + + + +# Server-side + + + +# Client-side + + + +blank COPYPOSTFIELDS and duphandle + + +lib%TESTNUMBER + + + +localhost + + + +# Verify data after the test has been "shot" + + + diff --git a/tests/libtest/Makefile.inc b/tests/libtest/Makefile.inc index 53439cab35f0..f02260a07eb5 100644 --- a/tests/libtest/Makefile.inc +++ b/tests/libtest/Makefile.inc @@ -242,6 +242,7 @@ TESTS_C = \ lib1662.c \ lib1678.c \ lib1686.c \ + lib1791.c \ lib1900.c \ lib1901.c \ lib1902.c \ diff --git a/tests/libtest/lib1791.c b/tests/libtest/lib1791.c new file mode 100644 index 000000000000..6c95b1890e3d --- /dev/null +++ b/tests/libtest/lib1791.c @@ -0,0 +1,59 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ +#include "first.h" + +static CURLcode test_lib1791(const char *URL) +{ + CURL *curl; + CURL *copy; + CURLcode result = CURLE_OK; + (void)URL; + + if(curl_global_init(CURL_GLOBAL_ALL) != CURLE_OK) { + curl_mfprintf(stderr, "curl_global_init() failed\n"); + return TEST_ERR_MAJOR_BAD; + } + + curl = curl_easy_init(); + if(!curl) { + curl_mfprintf(stderr, "curl_easy_init() failed\n"); + curl_global_cleanup(); + return TEST_ERR_MAJOR_BAD; + } + + (void)curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE_LARGE, (curl_off_t)0); + (void)curl_easy_setopt(curl, CURLOPT_COPYPOSTFIELDS, ""); + + copy = curl_easy_duphandle(curl); + + if(copy) + curl_easy_cleanup(copy); + else + result = TEST_ERR_FAILURE; + + curl_easy_cleanup(curl); + curl_global_cleanup(); + + return result; +} From 110936726e518ff843e0a2063db8143be07dd286 Mon Sep 17 00:00:00 2001 From: xhon-pelushi Date: Wed, 9 Sep 2026 20:54:00 -0400 Subject: [PATCH 092/481] tool_easysrc: reset the handle between --next operations The curl tool creates a fresh easy handle for every operation, but the --libcurl output reuses a single handle for all of them and never resets it, so options set for one operation silently apply to the following ones and the generated program does not do what the command line did. 19d0f4acfa9d fixed this for SSL verification by re-emitting those two options after --next. Everything else still leaks. For curl -H "X-First: yes" --compressed http://host/one \ --next http://host/two the generated program sends X-First and Accept-Encoding on the second transfer as well, while the command line sends them only on the first. Confirmed by compiling the generated program and comparing what both actually put on the wire against a local server: operation 1 operation 2 curl X-First, Accept-Encoding neither generated program X-First, Accept-Encoding X-First, Accept-Encoding Emit curl_easy_reset() between operations instead, which covers the general case: every option the next operation needs is already re-emitted after the reset, because the tool writes out its options per operation. The call is deferred rather than written straight after curl_easy_perform() so that it only appears when another operation actually follows: a single-operation command generates no reset at all, and none is emitted before the trailing curl_easy_cleanup(). Existing --libcurl coverage (tests 1400-1407, 1420, 1465, 1481) is all single-operation, and 1679 checks only the SSL options, so add test 1687 for the general case. It fails without this change. Test 1679's expected output gains the same curl_easy_reset() line, since it is a --next test too. Ref: #22701 Closes #22890 --- src/tool_easysrc.c | 26 ++++++++++++-- tests/data/Makefile.am | 2 ++ tests/data/data1679.c | 1 + tests/data/data1687.c | 82 ++++++++++++++++++++++++++++++++++++++++++ tests/data/test1687 | 75 ++++++++++++++++++++++++++++++++++++++ 5 files changed, 184 insertions(+), 2 deletions(-) create mode 100644 tests/data/data1687.c create mode 100644 tests/data/test1687 diff --git a/src/tool_easysrc.c b/src/tool_easysrc.c index d7862fcb2561..1c2a3361792a 100644 --- a/src/tool_easysrc.c +++ b/src/tool_easysrc.c @@ -42,6 +42,13 @@ int easysrc_mime_count; int easysrc_slist_count; static bool easysrc_performed; +/* Set once a transfer has been performed, so that the next generated + statement resets the handle first: the tool uses a fresh easy handle per + operation, the generated code reuses one. Deferred rather than emitted + right after curl_easy_perform() so that no reset is generated when nothing + follows. */ +static bool easysrc_pending_reset = FALSE; + static const char * const srchead[] = { "/********* Sample code generated by the curl command line tool **********", " * All curl_easy_setopt() options are documented at:", @@ -92,7 +99,15 @@ static void easysrc_free(void) CURLcode easysrc_add(struct slist_wc **plist, const char *line) { CURLcode result = CURLE_OK; - struct slist_wc *list = slist_wc_append(*plist, line); + struct slist_wc *list; + if(easysrc_pending_reset && (plist == &easysrc_code)) { + /* cleared first: the recursive call adds to easysrc_code as well */ + easysrc_pending_reset = FALSE; + result = easysrc_add(&easysrc_code, "curl_easy_reset(curl);"); + if(result) + return result; + } + list = slist_wc_append(*plist, line); if(!list) { easysrc_free(); result = CURLE_OUT_OF_MEMORY; @@ -165,12 +180,19 @@ CURLcode easysrc_perform(void) if(!result) easysrc_performed = TRUE; + /* Options set for this operation must not leak into the next one */ + if(!result) + easysrc_pending_reset = TRUE; + return result; } CURLcode easysrc_cleanup(void) { - CURLcode result = easysrc_add(&easysrc_code, "curl_easy_cleanup(curl);"); + CURLcode result; + /* nothing follows, so no reset is needed before the cleanup */ + easysrc_pending_reset = FALSE; + result = easysrc_add(&easysrc_code, "curl_easy_cleanup(curl);"); if(!result) result = easysrc_add(&easysrc_code, "curl = NULL;"); diff --git a/tests/data/Makefile.am b/tests/data/Makefile.am index f83ce4d022f3..1e0f14c67f8d 100644 --- a/tests/data/Makefile.am +++ b/tests/data/Makefile.am @@ -1708,6 +1708,7 @@ TESTCASES = \ test1684 \ test1685 \ test1686 \ + test1687 \ \ test1700 \ test1702 \ @@ -2157,6 +2158,7 @@ EXTRA_DIST = \ data1465.c \ data1481.c \ data1679.c \ + data1687.c \ data1705-1.md \ data1705-2.md \ data1705-3.md \ diff --git a/tests/data/data1679.c b/tests/data/data1679.c index 22144d18a5e3..4a98fca4dc4c 100644 --- a/tests/data/data1679.c +++ b/tests/data/data1679.c @@ -41,6 +41,7 @@ int main(int argc, char *argv[]) result = curl_easy_perform(curl); + curl_easy_reset(curl); curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L); curl_easy_setopt(curl, CURLOPT_BUFFERSIZE, 102400L); curl_easy_setopt(curl, CURLOPT_URL, "http://%HOSTIP:%HTTPPORT/we/want/%TESTNUMBER"); diff --git a/tests/data/data1687.c b/tests/data/data1687.c new file mode 100644 index 000000000000..94b7a020796d --- /dev/null +++ b/tests/data/data1687.c @@ -0,0 +1,82 @@ +/********* Sample code generated by the curl command line tool ********** + * All curl_easy_setopt() options are documented at: + * https://curl.se/libcurl/c/curl_easy_setopt.html + ************************************************************************/ +#include + +int main(int argc, char *argv[]) +{ + CURLcode result; + CURL *curl; + struct curl_slist *slist1; + + slist1 = NULL; + slist1 = curl_slist_append(slist1, "X-First: yes"); + + curl = curl_easy_init(); + curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L); + curl_easy_setopt(curl, CURLOPT_BUFFERSIZE, 102400L); + curl_easy_setopt(curl, CURLOPT_URL, "http://%HOSTIP:%HTTPPORT/we/want/%TESTNUMBER"); + curl_easy_setopt(curl, CURLOPT_HTTPHEADER, slist1); + curl_easy_setopt(curl, CURLOPT_USERAGENT, "curl/%VERSION"); + curl_easy_setopt(curl, CURLOPT_MAXREDIRS, 50L); + curl_easy_setopt(curl, CURLOPT_TCP_KEEPALIVE, 1L); + + /* Here is a list of options the curl code used that cannot get generated + as source easily. You may choose to either not use them or implement + them yourself. + + CURLOPT_DEBUGFUNCTION was set to a function pointer + CURLOPT_DEBUGDATA was set to an object pointer + CURLOPT_WRITEDATA was set to an object pointer + CURLOPT_WRITEFUNCTION was set to a function pointer + CURLOPT_READDATA was set to an object pointer + CURLOPT_READFUNCTION was set to a function pointer + CURLOPT_SEEKDATA was set to an object pointer + CURLOPT_SEEKFUNCTION was set to a function pointer + CURLOPT_HEADERFUNCTION was set to a function pointer + CURLOPT_HEADERDATA was set to an object pointer + CURLOPT_ERRORBUFFER was set to an object pointer + CURLOPT_STDERR was set to an object pointer + + */ + + result = curl_easy_perform(curl); + + curl_easy_reset(curl); + curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L); + curl_easy_setopt(curl, CURLOPT_BUFFERSIZE, 102400L); + curl_easy_setopt(curl, CURLOPT_URL, "http://%HOSTIP:%HTTPPORT/we/want/%TESTNUMBER"); + curl_easy_setopt(curl, CURLOPT_USERAGENT, "curl/%VERSION"); + curl_easy_setopt(curl, CURLOPT_MAXREDIRS, 50L); + curl_easy_setopt(curl, CURLOPT_TCP_KEEPALIVE, 1L); + + /* Here is a list of options the curl code used that cannot get generated + as source easily. You may choose to either not use them or implement + them yourself. + + CURLOPT_DEBUGFUNCTION was set to a function pointer + CURLOPT_DEBUGDATA was set to an object pointer + CURLOPT_WRITEDATA was set to an object pointer + CURLOPT_WRITEFUNCTION was set to a function pointer + CURLOPT_READDATA was set to an object pointer + CURLOPT_READFUNCTION was set to a function pointer + CURLOPT_SEEKDATA was set to an object pointer + CURLOPT_SEEKFUNCTION was set to a function pointer + CURLOPT_HEADERFUNCTION was set to a function pointer + CURLOPT_HEADERDATA was set to an object pointer + CURLOPT_ERRORBUFFER was set to an object pointer + CURLOPT_STDERR was set to an object pointer + + */ + + result = curl_easy_perform(curl); + + curl_easy_cleanup(curl); + curl = NULL; + curl_slist_free_all(slist1); + slist1 = NULL; + + return (int)result; +} +/**** End of sample code ****/ diff --git a/tests/data/test1687 b/tests/data/test1687 new file mode 100644 index 000000000000..128970f70deb --- /dev/null +++ b/tests/data/test1687 @@ -0,0 +1,75 @@ + + + + +HTTP +HTTP GET +--libcurl +--next + + + +# Server-side + + +HTTP/1.1 200 OK +Date: Thu, 29 Jul 2008 14:49:00 GMT +Server: test-server/fake +Content-Length: 0 +Connection: close + + + + +# Client-side + + +--libcurl + + +http + + +--libcurl resets the handle between --next operations + + +SSL_CERT_FILE + + +http://%HOSTIP:%HTTPPORT/we/want/%TESTNUMBER -H "X-First: yes" --libcurl %LOGDIR/test%TESTNUMBER.c --next http://%HOSTIP:%HTTPPORT/we/want/%TESTNUMBER + + + +# Verify data after the test has been "shot" + + +GET /we/want/%TESTNUMBER HTTP/1.1 +Host: %HOSTIP:%HTTPPORT +User-Agent: curl/%VERSION +Accept: */* +X-First: yes + +GET /we/want/%TESTNUMBER HTTP/1.1 +Host: %HOSTIP:%HTTPPORT +User-Agent: curl/%VERSION +Accept: */* + + + +# CURLOPT_SSL_VERIFYPEER, SSL_VERIFYHOST, SSLVERSION, SSH_KNOWNHOSTS and +# HTTP_VERSION vary with configurations, CURLOPT_INTERLEAVEDATA requires +# RTSP protocol - ignore them +$_ = '' if /CURLOPT_SSL_VERIFYPEER/ +$_ = '' if /CURLOPT_SSL_VERIFYHOST/ +$_ = '' if /CURLOPT_SSLVERSION/ +$_ = '' if /CURLOPT_SSH_KNOWNHOSTS/ +$_ = '' if /CURLOPT_HTTP_VERSION/ +$_ = '' if /CURLOPT_HTTP09_ALLOWED/ +$_ = '' if /CURLOPT_INTERLEAVEDATA/ +$_ = '' if /CURLOPT_TIMEOUT_MS/ + + +%includetext %SRCDIR/data/data%TESTNUMBER.c% + + + From 2dee5ea29416c498b6b9d21c897e9571ff3b3ec5 Mon Sep 17 00:00:00 2001 From: Stefan Eissing Date: Thu, 10 Sep 2026 09:46:53 +0200 Subject: [PATCH 093/481] mulit_ev: remove socket references on forget When multi_ev is informed that a socket is about to be closed, it cleans up its entry for it (if it exists), but it did not remove it from any remembered "last pollset"s. When the OS reuses the file descriptor, this may lead to socket identity confusion on detecting changes of use. Remove the socket from any pollset that is registered at the socket entry of multi_ev when a socket is being "forgotten". Found-by: Max Dymond Closes #22895 --- lib/multi_ev.c | 111 +++++++++++++++++++++++++++++-------------------- lib/select.c | 17 ++++++++ lib/select.h | 2 + 3 files changed, 84 insertions(+), 46 deletions(-) diff --git a/lib/multi_ev.c b/lib/multi_ev.c index a5636da9c1f9..7215ee25e383 100644 --- a/lib/multi_ev.c +++ b/lib/multi_ev.c @@ -131,6 +131,52 @@ static bool mev_sh_entry_conn_known(struct mev_sh_entry *e, return (e->conn == conn); } +static void mev_pollset_dtor(const void *key, size_t klen, void *entry) +{ + struct easy_pollset *ps = entry; + (void)key; + (void)klen; + if(ps) { + Curl_pollset_cleanup(ps); + curlx_free(ps); + } +} + +static struct easy_pollset *mev_add_new_conn_pollset(struct connectdata *conn) +{ + struct easy_pollset *ps; + + ps = Curl_pollset_create(); + if(!ps) + return NULL; + if(Curl_conn_meta_set(conn, CURL_META_MEV_POLLSET, ps, mev_pollset_dtor)) + return NULL; + return ps; +} + +static struct easy_pollset *mev_add_new_xfer_pollset(struct Curl_easy *data) +{ + struct easy_pollset *ps; + + ps = Curl_pollset_create(); + if(!ps) + return NULL; + if(Curl_meta_set(data, CURL_META_MEV_POLLSET, ps, mev_pollset_dtor)) + return NULL; + return ps; +} + +static struct easy_pollset *mev_get_last_pollset(struct Curl_easy *data, + struct connectdata *conn) +{ + if(data) { + if(conn) + return Curl_conn_meta_get(conn, CURL_META_MEV_POLLSET); + return Curl_meta_get(data, CURL_META_MEV_POLLSET); + } + return NULL; +} + static bool mev_sh_entry_xfer_add(struct mev_sh_entry *e, struct Curl_easy *data) { @@ -179,11 +225,30 @@ static CURLMcode mev_forget_socket(struct Curl_multi *multi, const char *cause) { struct mev_sh_entry *entry = mev_sh_entry_get(&multi->ev.sh_entries, s); + uint32_t mid; int rc = 0; if(!entry) /* we never knew or already forgot about this socket */ return CURLM_OK; + /* Remove the socket from any pollset that is still registered. */ + if(Curl_uint32_spbset_first(&entry->xfers, &mid)) { + do { + struct Curl_easy *sdata = Curl_multi_get_easy(multi, mid); + if(sdata) { + struct easy_pollset *ps = mev_get_last_pollset(sdata, NULL); + if(ps) + Curl_pollset_remove(ps, s); + } + } while(Curl_uint32_spbset_next(&entry->xfers, mid, &mid)); + } + + if(entry->conn) { + struct easy_pollset *ps = mev_get_last_pollset(data, entry->conn); + if(ps) + Curl_pollset_remove(ps, s); + } + /* We managed this socket before, tell the socket callback to forget it. */ if(entry->announced && multi->socket_cb) { struct Curl_mapi_guard guard; @@ -433,52 +498,6 @@ static CURLMcode mev_pollset_diff(struct Curl_multi *multi, return CURLM_OK; } -static void mev_pollset_dtor(const void *key, size_t klen, void *entry) -{ - struct easy_pollset *ps = entry; - (void)key; - (void)klen; - if(ps) { - Curl_pollset_cleanup(ps); - curlx_free(ps); - } -} - -static struct easy_pollset *mev_add_new_conn_pollset(struct connectdata *conn) -{ - struct easy_pollset *ps; - - ps = Curl_pollset_create(); - if(!ps) - return NULL; - if(Curl_conn_meta_set(conn, CURL_META_MEV_POLLSET, ps, mev_pollset_dtor)) - return NULL; - return ps; -} - -static struct easy_pollset *mev_add_new_xfer_pollset(struct Curl_easy *data) -{ - struct easy_pollset *ps; - - ps = Curl_pollset_create(); - if(!ps) - return NULL; - if(Curl_meta_set(data, CURL_META_MEV_POLLSET, ps, mev_pollset_dtor)) - return NULL; - return ps; -} - -static struct easy_pollset *mev_get_last_pollset(struct Curl_easy *data, - struct connectdata *conn) -{ - if(data) { - if(conn) - return Curl_conn_meta_get(conn, CURL_META_MEV_POLLSET); - return Curl_meta_get(data, CURL_META_MEV_POLLSET); - } - return NULL; -} - static CURLMcode mev_assess(struct Curl_multi *multi, struct Curl_easy *data, struct connectdata *conn) diff --git a/lib/select.c b/lib/select.c index f6ab6a6d4e90..a161d93d3205 100644 --- a/lib/select.c +++ b/lib/select.c @@ -632,6 +632,23 @@ CURLcode Curl_pollset_set(struct Curl_easy *data, (!do_out ? CURL_POLL_OUT : 0)); } +void Curl_pollset_remove(struct easy_pollset *ps, curl_socket_t sock) +{ + unsigned int i; + for(i = 0; i < ps->n; ++i) { + if(ps->sockets[i] == sock) { + if((i + 1) < ps->n) { + memmove(&ps->sockets[i], &ps->sockets[i + 1], + (ps->n - (i + 1)) * sizeof(ps->sockets[0])); + memmove(&ps->actions[i], &ps->actions[i + 1], + (ps->n - (i + 1)) * sizeof(ps->actions[0])); + } + --ps->n; + return; + } + } +} + /* * Return values: * -1 = error diff --git a/lib/select.h b/lib/select.h index 8fe519aee661..f2e9cddd6553 100644 --- a/lib/select.h +++ b/lib/select.h @@ -171,6 +171,8 @@ CURLcode Curl_pollset_set(struct Curl_easy *data, #define Curl_pollset_set_out_only(data, ps, sock) \ Curl_pollset_change(data, ps, sock, CURL_POLL_OUT, CURL_POLL_IN) +void Curl_pollset_remove(struct easy_pollset *ps, curl_socket_t sock); + /* return < = on error, 0 on timeout or how many sockets are ready */ int Curl_pollset_poll(struct Curl_easy *data, struct easy_pollset *ps, From e7ea5d10b31fcdde4c69c867793b5a396b975849 Mon Sep 17 00:00:00 2001 From: Thanniru Sai Teja Date: Fri, 11 Sep 2026 00:25:10 +0530 Subject: [PATCH 094/481] base64: optimize decode lookup table and quantum loop Closes #22903 --- lib/curlx/base64.c | 72 +++++++++++++++++++++++++++++----------------- 1 file changed, 46 insertions(+), 26 deletions(-) diff --git a/lib/curlx/base64.c b/lib/curlx/base64.c index 519b045cfe30..8cb955f1cfbd 100644 --- a/lib/curlx/base64.c +++ b/lib/curlx/base64.c @@ -37,12 +37,39 @@ const char curlx_base64encdec[] = static const char base64url[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"; -static const unsigned char decodetable[] = { - 62, 255, 255, 255, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 255, - 255, 255, 255, 255, 255, 255, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, - 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, - 255, 255, 255, 255, 255, 255, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, - 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51 +static const unsigned char decodetable[256] = { + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 62, 0xff, 0xff, 0xff, 63, + 52, 53, 54, 55, 56, 57, 58, 59, + 60, 61, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0, 1, 2, 3, 4, 5, 6, + 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, + 23, 24, 25, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 26, 27, 28, 29, 30, 31, 32, + 33, 34, 35, 36, 37, 38, 39, 40, + 41, 42, 43, 44, 45, 46, 47, 48, + 49, 50, 51, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }; /* * curlx_base64_decode() @@ -69,7 +96,6 @@ CURLcode curlx_base64_decode(const char *src, size_t rawlen = 0; unsigned char *pos; unsigned char *newstr; - unsigned char lookup[256]; *outptr = NULL; *outlen = 0; @@ -102,25 +128,19 @@ CURLcode curlx_base64_decode(const char *src, pos = newstr; - memset(lookup, 0xff, sizeof(lookup)); - memcpy(&lookup['+'], decodetable, sizeof(decodetable)); - /* Decode the complete quantums first */ for(i = 0; i < fullQuantums; i++) { - unsigned char val; - unsigned int x = 0; - int j; - - for(j = 0; j < 4; j++) { - val = lookup[(unsigned char)*src++]; - if(val == 0xff) /* bad symbol */ - goto bad; - x = (x << 6) | val; - } - pos[2] = x & 0xff; - pos[1] = (x >> 8) & 0xff; - pos[0] = (x >> 16) & 0xff; + unsigned char v0 = decodetable[(unsigned char)src[0]]; + unsigned char v1 = decodetable[(unsigned char)src[1]]; + unsigned char v2 = decodetable[(unsigned char)src[2]]; + unsigned char v3 = decodetable[(unsigned char)src[3]]; + if((v0 | v1 | v2 | v3) & 0x80) + goto bad; + pos[0] = (unsigned char)((v0 << 2) | (v1 >> 4)); + pos[1] = (unsigned char)((v1 << 4) | (v2 >> 2)); + pos[2] = (unsigned char)((v2 << 6) | v3); pos += 3; + src += 4; } if(padding) { /* this means either 8 or 16 bits output */ @@ -137,15 +157,15 @@ CURLcode curlx_base64_decode(const char *src, goto bad; } else { - val = lookup[(unsigned char)*src++]; + val = decodetable[(unsigned char)*src++]; if(val == 0xff) /* bad symbol */ goto bad; x = (x << 6) | val; } } if(padding == 1) - pos[1] = (x >> 8) & 0xff; - pos[0] = (x >> 16) & 0xff; + pos[1] = (unsigned char)((x >> 8) & 0xff); + pos[0] = (unsigned char)((x >> 16) & 0xff); pos += 3 - padding; } From 5ddf5f9337616c20177f8e472675c7f3347f9c2b Mon Sep 17 00:00:00 2001 From: Daniel Stenberg Date: Thu, 10 Sep 2026 10:40:47 +0200 Subject: [PATCH 095/481] CURLOPT_SSL_CTX_FUNCTION.md: client cert caveats Basically this option is one big footgun Closes #22899 --- docs/libcurl/opts/CURLOPT_SSL_CTX_FUNCTION.md | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/docs/libcurl/opts/CURLOPT_SSL_CTX_FUNCTION.md b/docs/libcurl/opts/CURLOPT_SSL_CTX_FUNCTION.md index c834cc8bf523..13e84b0ae7a9 100644 --- a/docs/libcurl/opts/CURLOPT_SSL_CTX_FUNCTION.md +++ b/docs/libcurl/opts/CURLOPT_SSL_CTX_FUNCTION.md @@ -70,13 +70,25 @@ anything about it, which then subsequently can lead to libcurl unknowingly reusing SSL connections with different properties. To remedy this you may set CURLOPT_FORBID_REUSE(3) from the callback function. -A connection that is set up with this callback can be put in the connection +Note that this callback is not only invoked for a connection to the transfer's +URL, but also for DoH requests or redirects (if configured) or if happy +eyeballing is done, for example performing HTTP/2 and/or HTTP/3 connects in +parallel. + +Connections that are set up with this callback can be put in the connection pool by libcurl and then reused in following transfers without the callback -being called. The connection may even be selected from the pool to be used for +being called. The connections may even be selected from the pool to be used for transfers not using this callback. If the callback should only be valid for the specific transfer the callback verifies, it should be marked unsuitable for reuse with CURLOPT_FORBID_REUSE(3). +We strongly discourage setting a client certificate with this option, or any +other sensitive data. The application must prevent both connection and TLS +session reuse when these settings should not outlive the transfer. In addition +to setting CURLOPT_FORBID_REUSE(3), set CURLOPT_SSL_SESSIONID_CACHE(3) to 0 +because a new connection can resume a session established with these settings +even though the callback is invoked again. + If you are using DNS-over-HTTPS (DoH) via CURLOPT_DOH_URL(3) then this callback is also called for those transfers and the curl handle is set to an internal handle. **This behavior is subject to change.** We recommend setting From f71bc88140a8e5309773655183d3b0d796f3cb7f Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 10 Sep 2026 03:32:31 +0000 Subject: [PATCH 096/481] GHA: update awslabs/aws-lc to v5.8.0 Closes #22892 --- .github/workflows/http3-linux.yml | 2 +- .github/workflows/linux.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/http3-linux.yml b/.github/workflows/http3-linux.yml index ba5303b3064c..c33dca342419 100644 --- a/.github/workflows/http3-linux.yml +++ b/.github/workflows/http3-linux.yml @@ -39,7 +39,7 @@ env: CURL_TEST_MIN: 1850 DO_NOT_TRACK: '1' # renovate: datasource=github-tags depName=awslabs/aws-lc versioning=semver registryUrl=https://github.com - AWSLC_VERSION: 5.5.0 + AWSLC_VERSION: 5.8.0 # renovate: datasource=github-tags depName=google/boringssl versioning=semver registryUrl=https://github.com BORINGSSL_VERSION: 0.20260903.0 # renovate: datasource=github-tags depName=gnutls/nettle versioning=semver registryUrl=https://github.com diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 34be80e8e4e0..7d7396dee649 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -37,7 +37,7 @@ env: CURL_TEST_MIN: 1660 DO_NOT_TRACK: '1' # renovate: datasource=github-tags depName=awslabs/aws-lc versioning=semver registryUrl=https://github.com - AWSLC_VERSION: 5.5.0 + AWSLC_VERSION: 5.8.0 # renovate: datasource=github-tags depName=google/boringssl versioning=semver registryUrl=https://github.com BORINGSSL_VERSION: 0.20260903.0 # renovate: datasource=github-releases depName=pizlonator/fil-c versioning=semver-coerced registryUrl=https://github.com From ca6d0a8ee88b5d778c7d2a327928d7f05da2de7f Mon Sep 17 00:00:00 2001 From: Stefan Eissing Date: Fri, 4 Sep 2026 12:37:17 +0200 Subject: [PATCH 097/481] http proxy: some code cleanups - dedup code for CONNECT/CONNECT-UDP - pass proxy peer to h1/h2 filters for use in CONNECT reuqests (UDP needs it) Closes #22829 --- lib/cf-h1-proxy.c | 176 ++++++++++++------------------------ lib/cf-h1-proxy.h | 1 + lib/cf-h2-proxy.c | 7 +- lib/cf-h2-proxy.h | 1 + lib/http_proxy.c | 73 +++++++-------- lib/http_proxy.h | 2 +- lib/vquic/cf-ngtcp2-proxy.c | 24 ++--- 7 files changed, 112 insertions(+), 172 deletions(-) diff --git a/lib/cf-h1-proxy.c b/lib/cf-h1-proxy.c index 71472cb3af4d..3d0083a5c84b 100644 --- a/lib/cf-h1-proxy.c +++ b/lib/cf-h1-proxy.c @@ -72,12 +72,13 @@ struct h1_tunnel_state { BIT(close_connection); BIT(maybe_folded); BIT(leading_unfold); + BIT(udp_tunnel); }; /* Persistent context for the H1-PROXY filter */ struct cf_h1_proxy_ctx { + struct Curl_peer *peer; struct h1_tunnel_state *ts; - BIT(udp_tunnel); }; static bool tunnel_is_established(struct h1_tunnel_state *ts) @@ -93,7 +94,7 @@ static bool tunnel_is_failed(struct h1_tunnel_state *ts) static bool h1_proxy_is_udp(struct Curl_cfilter *cf) { struct cf_h1_proxy_ctx *pctx = cf->ctx; - return (pctx->udp_tunnel ? TRUE : FALSE); + return (bool)(pctx->ts && pctx->ts->udp_tunnel); } static CURLcode tunnel_reinit(struct Curl_cfilter *cf, @@ -232,6 +233,7 @@ static CURLcode start_CONNECT(struct Curl_cfilter *cf, struct Curl_easy *data, struct h1_tunnel_state *ts) { + struct cf_h1_proxy_ctx *pctx = cf->ctx; struct httpreq *req = NULL; int http_minor; CURLcode result; @@ -241,9 +243,10 @@ static CURLcode start_CONNECT(struct Curl_cfilter *cf, and we do not really use the newly cloned URL here then. Free it. */ curlx_safefree(data->req.newurl); - result = Curl_http_proxy_create_tunnel_request(&req, cf, data, ts->dest, - PROXY_HTTP_V1, - h1_proxy_is_udp(cf)); + result = Curl_http_proxy_create_tunnel_request(&req, cf, data, + pctx->peer, ts->dest, + PROXY_HTTP_V1, + h1_proxy_is_udp(cf)); if(result) goto out; @@ -299,91 +302,6 @@ static CURLcode send_CONNECT(struct Curl_cfilter *cf, return result; } -static CURLcode on_resp_header_udp(struct Curl_cfilter *cf, - struct Curl_easy *data, - struct h1_tunnel_state *ts, - const char *header) -{ - CURLcode result = CURLE_OK; - struct SingleRequest *k = &data->req; - - if(checkprefix("Proxy-authenticate:", header) && (407 == k->httpcode)) { - - bool proxy = (k->httpcode == 407); - char *auth = Curl_copy_header_value(header); - if(!auth) - return CURLE_OUT_OF_MEMORY; - - CURL_TRC_CF(data, cf, "CONNECT-UDP: fwd auth header '%s'", header); - result = Curl_http_input_auth(data, proxy, auth); - - curlx_free(auth); - - if(result) - return result; - } - else if(checkprefix("Content-Length:", header)) { - if(k->httpcode / 100 == 2 || k->httpcode == 101) { - infof(data, "Ignoring Content-Length in CONNECT-UDP %03d response", - k->httpcode); - } - else { - const char *p = header + CURL_CSTRLEN("Content-Length:"); - if(curlx_str_numblanks(&p, &ts->cl)) { - failf(data, "Unsupported Content-Length value"); - return CURLE_WEIRD_SERVER_REPLY; - } - } - } - else if(checkprefix("Transfer-Encoding:", header)) { - if(k->httpcode / 100 == 2 || k->httpcode == 101) { - infof(data, "Ignoring Transfer-Encoding in " - "CONNECT-UDP %03d response", k->httpcode); - } - else if(Curl_compareheader(header, - STRCONST("Transfer-Encoding:"), - STRCONST("chunked"))) { - CURL_TRC_CF(data, cf, "CONNECT-UDP Response --> " - "Transfer-Encoding: chunked"); - ts->chunked_encoding = TRUE; - /* reset our chunky engine */ - Curl_httpchunk_reset(data, &ts->ch, TRUE); - } - } - else if(checkprefix("Capsule-protocol:", header)) { - if(Curl_compareheader(header, - STRCONST("Capsule-protocol:"), - STRCONST("?1"))) { - CURL_TRC_CF(data, cf, "CONNECT-UDP Response --> Capsule-protocol: ?1"); - } - } - else if(Curl_compareheader(header, - STRCONST("Connection:"), STRCONST("close"))) { - ts->close_connection = TRUE; - CURL_TRC_CF(data, cf, "CONNECT-UDP Response --> Connection: close"); - } - else if(Curl_compareheader(header, - STRCONST("Proxy-Connection:"), - STRCONST("close"))) { - ts->close_connection = TRUE; - CURL_TRC_CF(data, cf, - "CONNECT-UDP Response --> Proxy-Connection: close"); - } - else if(!strncmp(header, "HTTP/1.", 7) && - ((header[7] == '0') || (header[7] == '1')) && - (header[8] == ' ') && - ISDIGIT(header[9]) && ISDIGIT(header[10]) && ISDIGIT(header[11]) && - !ISDIGIT(header[12])) { - /* store the HTTP code from the proxy */ - data->info.httpproxycode = k->httpcode = - ((header[9] - '0') * 100) + - ((header[10] - '0') * 10) + - (header[11] - '0'); - CURL_TRC_CF(data, cf, "CONNECT-UDP Response --> %d", k->httpcode); - } - return result; -} - static CURLcode on_resp_header(struct Curl_cfilter *cf, struct Curl_easy *data, struct h1_tunnel_state *ts, @@ -391,7 +309,7 @@ static CURLcode on_resp_header(struct Curl_cfilter *cf, { CURLcode result = CURLE_OK; struct SingleRequest *k = &data->req; - (void)cf; + bool is_udp = h1_proxy_is_udp(cf); if(checkprefix("Proxy-authenticate:", header) && (407 == k->httpcode)) { @@ -400,7 +318,8 @@ static CURLcode on_resp_header(struct Curl_cfilter *cf, if(!auth) return CURLE_OUT_OF_MEMORY; - CURL_TRC_CF(data, cf, "CONNECT: fwd auth header '%s'", header); + CURL_TRC_CF(data, cf, "CONNECT%s: fwd auth header '%s'", + is_udp ? "-UDP" : "", header); result = Curl_http_input_auth(data, proxy, auth); curlx_free(auth); @@ -409,12 +328,17 @@ static CURLcode on_resp_header(struct Curl_cfilter *cf, return result; } else if(checkprefix("Content-Length:", header)) { - if(k->httpcode / 100 == 2) { + if(k->httpcode < 300) { + if(k->httpcode < 200) { + /* Informational 1xx responses cannot carry a body. RFC 9110 15.2 */ + failf(data, "Invalid Content-Length: in %03d response", k->httpcode); + return CURLE_WEIRD_SERVER_REPLY; + } /* A client MUST ignore any Content-Length or Transfer-Encoding header fields received in a successful response to CONNECT. "Successful" described as: 2xx (Successful). RFC 7231 4.3.6 */ - infof(data, "Ignoring Content-Length in CONNECT %03d response", - k->httpcode); + infof(data, "Ignoring Content-Length in CONNECT%s %03d response", + is_udp ? "-UDP" : "", k->httpcode); } else { const char *p = header + CURL_CSTRLEN("Content-Length:"); @@ -425,29 +349,47 @@ static CURLcode on_resp_header(struct Curl_cfilter *cf, } } else if(Curl_compareheader(header, - STRCONST("Connection:"), STRCONST("close"))) + STRCONST("Connection:"), STRCONST("close"))) { + CURL_TRC_CF(data, cf, "CONNECT%s Connection: close", is_udp ? "-UDP" : ""); ts->close_connection = TRUE; + } else if(checkprefix("Transfer-Encoding:", header)) { - if(k->httpcode / 100 == 2) { + if(k->httpcode < 300) { + if(k->httpcode < 200) { + /* Informational 1xx responses cannot carry a body. RFC 9110 15.2 */ + failf(data, "Invalid Transfer-Encoding: in %03d response", + k->httpcode); + return CURLE_WEIRD_SERVER_REPLY; + } /* A client MUST ignore any Content-Length or Transfer-Encoding header fields received in a successful response to CONNECT. "Successful" described as: 2xx (Successful). RFC 7231 4.3.6 */ infof(data, "Ignoring Transfer-Encoding in " - "CONNECT %03d response", k->httpcode); + "CONNECT%s %03d response", is_udp ? "-UDP" : "", k->httpcode); } else if(Curl_compareheader(header, STRCONST("Transfer-Encoding:"), STRCONST("chunked"))) { - infof(data, "CONNECT responded chunked"); + CURL_TRC_CF(data, cf, "CONNECT%s response chunked", + is_udp ? "-UDP" : ""); ts->chunked_encoding = TRUE; - /* reset our chunky engine */ Curl_httpchunk_reset(data, &ts->ch, TRUE); } } + else if(is_udp && checkprefix("Capsule-protocol:", header)) { + if(Curl_compareheader(header, + STRCONST("Capsule-protocol:"), + STRCONST("?1"))) { + CURL_TRC_CF(data, cf, "CONNECT-UDP Response --> Capsule-protocol: ?1"); + } + } else if(Curl_compareheader(header, STRCONST("Proxy-Connection:"), - STRCONST("close"))) + STRCONST("close"))) { + CURL_TRC_CF(data, cf, "CONNECT%s Proxy-Connection: close", + is_udp ? "-UDP" : ""); ts->close_connection = TRUE; + } else if(!strncmp(header, "HTTP/1.", 7) && ((header[7] == '0') || (header[7] == '1')) && (header[8] == ' ') && @@ -456,6 +398,8 @@ static CURLcode on_resp_header(struct Curl_cfilter *cf, /* store the HTTP code from the proxy */ data->info.httpproxycode = k->httpcode = ((header[9] - '0') * 100) + ((header[10] - '0') * 10) + (header[11] - '0'); + CURL_TRC_CF(data, cf, "CONNECT%s HTTP status %d", + is_udp ? "-UDP" : "", k->httpcode); } return result; } @@ -527,13 +471,7 @@ static CURLcode single_header(struct Curl_cfilter *cf, return result; } - if(h1_proxy_is_udp(cf)) { - result = on_resp_header_udp(cf, data, ts, linep); - } - else { - result = on_resp_header(cf, data, ts, linep); - } - + result = on_resp_header(cf, data, ts, linep); if(result) return result; @@ -802,12 +740,8 @@ static CURLcode H1_CONNECT(struct Curl_cfilter *cf, /* 2xx response, SUCCESS! */ /* 101 Switching Protocol for CONNECT-UDP */ h1_tunnel_go_state(cf, ts, H1_TUNNEL_ESTABLISHED, data); - if(h1_proxy_is_udp(cf)) - infof(data, "CONNECT-UDP tunnel established, response %d", - data->info.httpproxycode); - else - infof(data, "CONNECT tunnel established, response %d", - data->info.httpproxycode); + infof(data, "CONNECT%s tunnel established, response %d", + h1_proxy_is_udp(cf) ? "-UDP" : "", data->info.httpproxycode); result = CURLE_OK; out: @@ -903,9 +837,14 @@ static bool cf_h1_proxy_data_pending(struct Curl_cfilter *cf, static void cf_h1_proxy_destroy(struct Curl_cfilter *cf, struct Curl_easy *data) { + struct cf_h1_proxy_ctx *pctx = cf->ctx; + CURL_TRC_CF(data, cf, "destroy"); - cf_tunnel_free(cf, data); - curlx_safefree(cf->ctx); + if(pctx) { + cf_tunnel_free(cf, data); + Curl_peer_unlink(&pctx->peer); + curlx_safefree(cf->ctx); + } } static CURLcode cf_h1_proxy_query(struct Curl_cfilter *cf, @@ -954,6 +893,7 @@ struct Curl_cftype Curl_cft_h1_proxy = { CURLcode Curl_cf_h1_proxy_insert_after(struct Curl_cfilter *cf_at, struct Curl_easy *data, + struct Curl_peer *peer, struct Curl_peer *dest, int httpversion, bool udp_tunnel) @@ -975,6 +915,7 @@ CURLcode Curl_cf_h1_proxy_insert_after(struct Curl_cfilter *cf_at, goto out; } Curl_peer_link(&ts->dest, dest); + ts->udp_tunnel = udp_tunnel; ts->httpversion = httpversion; curlx_dyn_init(&ts->rcvbuf, DYN_PROXY_CONNECT_HEADERS); curlx_dyn_init(&ts->request_data, DYN_HTTP_REQUEST); @@ -985,10 +926,11 @@ CURLcode Curl_cf_h1_proxy_insert_after(struct Curl_cfilter *cf_at, result = CURLE_OUT_OF_MEMORY; goto out; } - pctx->udp_tunnel = udp_tunnel; + Curl_peer_link(&pctx->peer, peer); pctx->ts = ts; result = Curl_cf_create(&cf, &Curl_cft_h1_proxy, pctx); if(result) { + Curl_peer_unlink(&pctx->peer); curlx_free(pctx); goto out; } diff --git a/lib/cf-h1-proxy.h b/lib/cf-h1-proxy.h index 3255bf79f240..3db05822ce77 100644 --- a/lib/cf-h1-proxy.h +++ b/lib/cf-h1-proxy.h @@ -31,6 +31,7 @@ struct Curl_peer; CURLcode Curl_cf_h1_proxy_insert_after(struct Curl_cfilter *cf_at, struct Curl_easy *data, + struct Curl_peer *peer, struct Curl_peer *dest, int httpversion, bool udp_tunnel); diff --git a/lib/cf-h2-proxy.c b/lib/cf-h2-proxy.c index e4950862ead0..6dc45acf64bd 100644 --- a/lib/cf-h2-proxy.c +++ b/lib/cf-h2-proxy.c @@ -184,6 +184,7 @@ struct cf_h2_proxy_ctx { struct bufq inbufq; /* network receive buffer */ struct bufq outbufq; /* network send buffer */ + struct Curl_peer *peer; /* proxy we talk to */ struct Curl_peer *dest; /* where to tunnel to */ struct tunnel_stream tunnel; /* our tunnel CONNECT stream */ int32_t goaway_error; @@ -208,6 +209,7 @@ static void cf_h2_proxy_ctx_clear(struct cf_h2_proxy_ctx *ctx) } Curl_bufq_free(&ctx->inbufq); Curl_bufq_free(&ctx->outbufq); + Curl_peer_unlink(&ctx->peer); Curl_peer_unlink(&ctx->dest); tunnel_stream_clear(&ctx->tunnel); memset(ctx, 0, sizeof(*ctx)); @@ -770,7 +772,8 @@ static CURLcode submit_CONNECT(struct Curl_cfilter *cf, CURLcode result; struct httpreq *req = NULL; - result = Curl_http_proxy_create_tunnel_request(&req, cf, data, ctx->dest, + result = Curl_http_proxy_create_tunnel_request(&req, cf, data, + ctx->peer, ctx->dest, PROXY_HTTP_V2, (bool)ctx->udp_tunnel); if(result) @@ -1484,6 +1487,7 @@ struct Curl_cftype Curl_cft_h2_proxy = { CURLcode Curl_cf_h2_proxy_insert_after(struct Curl_cfilter *cf, struct Curl_easy *data, + struct Curl_peer *peer, struct Curl_peer *dest, bool udp_tunnel) { @@ -1495,6 +1499,7 @@ CURLcode Curl_cf_h2_proxy_insert_after(struct Curl_cfilter *cf, ctx = curlx_calloc(1, sizeof(*ctx)); if(!ctx) goto out; + Curl_peer_link(&ctx->peer, peer); Curl_peer_link(&ctx->dest, dest); ctx->udp_tunnel = udp_tunnel; diff --git a/lib/cf-h2-proxy.h b/lib/cf-h2-proxy.h index 07e3c9aedf1a..4b1dde26496d 100644 --- a/lib/cf-h2-proxy.h +++ b/lib/cf-h2-proxy.h @@ -29,6 +29,7 @@ CURLcode Curl_cf_h2_proxy_insert_after(struct Curl_cfilter *cf, struct Curl_easy *data, + struct Curl_peer *peer, struct Curl_peer *dest, bool udp_tunnel); diff --git a/lib/http_proxy.c b/lib/http_proxy.c index f620acb0cdb5..78b4a2e7bc4b 100644 --- a/lib/http_proxy.c +++ b/lib/http_proxy.c @@ -197,6 +197,7 @@ static int proxy_http_ver_major(proxy_http_ver ver) static CURLcode http_proxy_create_CONNECT(struct httpreq **preq, struct Curl_cfilter *cf, struct Curl_easy *data, + struct Curl_peer *peer, struct Curl_peer *dest, proxy_http_ver ver) { @@ -206,6 +207,7 @@ static CURLcode http_proxy_create_CONNECT(struct httpreq **preq, CURLcode result; struct httpreq *req = NULL; + (void)peer; authority = curl_maprintf("%s%s%s:%u", dest->ipv6 ? "[" : "", dest->hostname, @@ -216,7 +218,7 @@ static CURLcode http_proxy_create_CONNECT(struct httpreq **preq, goto out; } - result = Curl_http_req_make(&req, "CONNECT", CURL_CSTRLEN("CONNECT"), + result = Curl_http_req_make(&req, STRCONST("CONNECT"), NULL, 0, authority, strlen(authority), NULL, 0); if(result) @@ -274,17 +276,16 @@ static CURLcode http_proxy_create_CONNECT(struct httpreq **preq, static CURLcode http_proxy_create_CONNECTUDP(struct httpreq **preq, struct Curl_cfilter *cf, struct Curl_easy *data, + struct Curl_peer *peer, struct Curl_peer *dest, proxy_http_ver ver) { const char *proxy_scheme = "http", *ua; - const char *proxy_host = cf->conn->http_proxy.peer->hostname; int httpversion = proxy_http_ver_major(ver); char *authority = NULL; char *path = NULL; char *encoded_host = NULL; struct httpreq *req = NULL; - bool proxy_ipv6_ip; CURLcode result; if(cf->conn->http_proxy.proxytype == CURLPROXY_HTTPS || @@ -292,13 +293,11 @@ static CURLcode http_proxy_create_CONNECTUDP(struct httpreq **preq, cf->conn->http_proxy.proxytype == CURLPROXY_HTTPS3) proxy_scheme = "https"; - proxy_ipv6_ip = cf->conn->http_proxy.peer->ipv6 != 0; - authority = curl_maprintf("%s%s%s:%d", - proxy_ipv6_ip ? "[" : "", - proxy_host, - proxy_ipv6_ip ? "]" : "", - cf->conn->http_proxy.peer->port); + peer->ipv6 ? "[" : "", + peer->hostname, + peer->ipv6 ? "]" : "", + peer->port); if(!authority) { result = CURLE_OUT_OF_MEMORY; goto out; @@ -349,7 +348,7 @@ static CURLcode http_proxy_create_CONNECTUDP(struct httpreq **preq, goto out; } else if(ver == PROXY_HTTP_V2 || ver == PROXY_HTTP_V3) { - result = Curl_http_req_make(&req, "CONNECT", CURL_CSTRLEN("CONNECT"), + result = Curl_http_req_make(&req, STRCONST("CONNECT"), proxy_scheme, strlen(proxy_scheme), authority, strlen(authority), path, strlen(path)); @@ -440,28 +439,20 @@ static CURLcode http_proxy_create_CONNECTUDP(struct httpreq **preq, CURLcode Curl_http_proxy_create_tunnel_request( struct httpreq **preq, struct Curl_cfilter *cf, - struct Curl_easy *data, struct Curl_peer *dest, + struct Curl_easy *data, struct Curl_peer *peer, struct Curl_peer *dest, proxy_http_ver ver, bool udp_tunnel) { - CURLcode result; - - if(udp_tunnel) - result = http_proxy_create_CONNECTUDP(preq, cf, data, dest, ver); - else - result = http_proxy_create_CONNECT(preq, cf, data, dest, ver); + CURLcode result = udp_tunnel ? + http_proxy_create_CONNECTUDP(preq, cf, data, peer, dest, ver) : + http_proxy_create_CONNECT(preq, cf, data, peer, dest, ver); if(result) return result; - if(udp_tunnel) - infof(data, "Establishing %s proxy UDP tunnel to %s:%u", - (ver == PROXY_HTTP_V2) ? "HTTP/2" : - (ver == PROXY_HTTP_V3) ? "HTTP/3" : "HTTP", - dest->user_hostname, dest->port); - else - infof(data, "Establishing %s proxy tunnel to %s", - (ver == PROXY_HTTP_V2) ? "HTTP/2" : - (ver == PROXY_HTTP_V3) ? "HTTP/3" : "HTTP", - (*preq)->authority); + infof(data, "Establishing %s proxy %stunnel to %s:%u", + (ver == PROXY_HTTP_V2) ? "HTTP/2" : + (ver == PROXY_HTTP_V3) ? "HTTP/3" : "HTTP", + udp_tunnel ? "UDP " : "", + dest->user_hostname, dest->port); return CURLE_OK; } @@ -478,10 +469,8 @@ CURLcode Curl_http_proxy_inspect_tunnel_response( DEBUGASSERT(resp); header_count = Curl_dynhds_count(&resp->headers); - if(udp_tunnel) - infof(data, "CONNECT-UDP Response Status %d", resp->status); - else - infof(data, "CONNECT Response Status %d", resp->status); + infof(data, "CONNECT%s Response Status %d", + udp_tunnel ? "-UDP" : "", resp->status); infof(data, "Response Headers (%zu total):", header_count); for(i = 0; i < header_count; i++) { struct dynhds_entry *entry = Curl_dynhds_getn(&resp->headers, i); @@ -558,14 +547,13 @@ static CURLcode http_proxy_cf_connect(struct Curl_cfilter *cf, struct cf_proxy_ctx *ctx = cf->ctx; CURLcode result; bool udp_tunnel = TRNSPRT_IS_DGRAM(ctx->tunnel_transport); - const char *tunnel_type = udp_tunnel ? "CONNECT-UDP" : "CONNECT"; if(cf->connected) { *done = TRUE; return CURLE_OK; } - CURL_TRC_CF(data, cf, "%s", tunnel_type); + CURL_TRC_CF(data, cf, "CONNECT%s", udp_tunnel ? "-UDP" : ""); connect_sub: /* in case of h3_proxy, cf->next will be NULL initially */ if(cf->next) { @@ -584,10 +572,11 @@ static CURLcode http_proxy_cf_connect(struct Curl_cfilter *cf, } if(alpn) - infof(data, "%s: '%s' negotiated", tunnel_type, alpn); + infof(data, "CONNECT%s: '%s' negotiated", + udp_tunnel ? "-UDP" : "", alpn); else if(!alpn) { /* No ALPN, proxytype rules. Fake ALPN */ - infof(data, "%s: no ALPN negotiated", tunnel_type); + infof(data, "CONNECT%s: no ALPN negotiated", udp_tunnel ? "-UDP" : ""); switch(ctx->proxytype) { case CURLPROXY_HTTP_1_0: alpn = "http/1.0"; @@ -606,8 +595,8 @@ static CURLcode http_proxy_cf_connect(struct Curl_cfilter *cf, if(!strcmp(alpn, "http/1.0")) { CURL_TRC_CF(data, cf, "installing subfilter for HTTP/1.0"); - result = Curl_cf_h1_proxy_insert_after(cf, data, ctx->tunnel_peer, 10, - udp_tunnel); + result = Curl_cf_h1_proxy_insert_after(cf, data, ctx->peer, + ctx->tunnel_peer, 10, udp_tunnel); if(result) goto out; } @@ -615,7 +604,8 @@ static CURLcode http_proxy_cf_connect(struct Curl_cfilter *cf, int httpversion = (ctx->proxytype == CURLPROXY_HTTP_1_0) ? 10 : 11; CURL_TRC_CF(data, cf, "installing subfilter for HTTP/1.%d", httpversion % 10); - result = Curl_cf_h1_proxy_insert_after(cf, data, ctx->tunnel_peer, + result = Curl_cf_h1_proxy_insert_after(cf, data, ctx->peer, + ctx->tunnel_peer, httpversion, udp_tunnel); if(result) goto out; @@ -623,8 +613,8 @@ static CURLcode http_proxy_cf_connect(struct Curl_cfilter *cf, #ifdef USE_NGHTTP2 else if(!strcmp(alpn, "h2")) { CURL_TRC_CF(data, cf, "installing subfilter for HTTP/2"); - result = Curl_cf_h2_proxy_insert_after(cf, data, ctx->tunnel_peer, - udp_tunnel); + result = Curl_cf_h2_proxy_insert_after(cf, data, ctx->peer, + ctx->tunnel_peer, udp_tunnel); if(result) goto out; } @@ -641,7 +631,8 @@ static CURLcode http_proxy_cf_connect(struct Curl_cfilter *cf, } #endif /* USE_PROXY_HTTP3 && USE_NGHTTP3 && USE_NGTCP2 && USE_OPENSSL */ else { - failf(data, "%s: negotiated ALPN '%s' not supported", tunnel_type, alpn); + failf(data, "CONNECT%s: negotiated ALPN '%s' not supported", + udp_tunnel ? "-UDP" : "", alpn); result = CURLE_COULDNT_CONNECT; goto out; } diff --git a/lib/http_proxy.h b/lib/http_proxy.h index 86c908809303..7f5fc9444050 100644 --- a/lib/http_proxy.h +++ b/lib/http_proxy.h @@ -53,7 +53,7 @@ typedef enum { /* Create CONNECT or CONNECT-UDP request */ CURLcode Curl_http_proxy_create_tunnel_request( struct httpreq **preq, struct Curl_cfilter *cf, - struct Curl_easy *data, struct Curl_peer *dest, + struct Curl_easy *data, struct Curl_peer *peer, struct Curl_peer *dest, proxy_http_ver ver, bool udp_tunnel); /* Inspect tunnel response for H2/H3 proxy (capsule-protocol, auth) */ diff --git a/lib/vquic/cf-ngtcp2-proxy.c b/lib/vquic/cf-ngtcp2-proxy.c index 89bf19cc2782..3b2cf8425e5a 100644 --- a/lib/vquic/cf-ngtcp2-proxy.c +++ b/lib/vquic/cf-ngtcp2-proxy.c @@ -57,7 +57,7 @@ typedef enum { } h3_tunnel_state; struct h3_tunnel_stream { - struct Curl_peer *peer; /* where the tunnel goes to */ + struct Curl_peer *dest; /* where the tunnel goes to */ struct http_resp *resp; struct bufq recvbuf; char *authority; @@ -69,19 +69,19 @@ struct h3_tunnel_stream { }; static CURLcode h3_tunnel_stream_init(struct h3_tunnel_stream *ts, - struct Curl_peer *peer, + struct Curl_peer *dest, bool udp) { ts->state = H3_TUNNEL_INIT; - Curl_peer_link(&ts->peer, peer); + Curl_peer_link(&ts->dest, dest); Curl_bufq_init2(&ts->recvbuf, H3_STREAM_CHUNK_SIZE, PROXY_H3_STREAM_RECV_CHUNKS, BUFQ_OPT_SOFT_LIMIT); ts->udp = udp; /* host:port with IPv6 support */ - ts->authority = curl_maprintf("%s%s%s:%u", peer->ipv6 ? "[" : "", - peer->hostname, - peer->ipv6 ? "]" : "", - peer->port); + ts->authority = curl_maprintf("%s%s%s:%u", dest->ipv6 ? "[" : "", + dest->hostname, + dest->ipv6 ? "]" : "", + dest->port); if(!ts->authority) return CURLE_OUT_OF_MEMORY; @@ -101,7 +101,7 @@ static void h3_tunnel_stream_reset(struct h3_tunnel_stream *ts) static void h3_tunnel_stream_cleanup(struct h3_tunnel_stream *ts) { - Curl_peer_unlink(&ts->peer); + Curl_peer_unlink(&ts->dest); Curl_bufq_free(&ts->recvbuf); Curl_http_resp_free(ts->resp); curlx_safefree(ts->authority); @@ -1044,13 +1044,13 @@ static CURLcode cf_h3_proxy_submit_CONNECT(struct Curl_cfilter *cf, struct Curl_easy *data, struct h3_tunnel_stream *ts) { + struct cf_h3_proxy_ctx *ctx = cf->ctx; CURLcode result; struct httpreq *req = NULL; - result = Curl_http_proxy_create_tunnel_request(&req, cf, data, - ts->peer, - PROXY_HTTP_V3, - (bool)ts->udp); + result = Curl_http_proxy_create_tunnel_request( + &req, cf, data, ctx->ngtcp2_ctx.ssl_peer.peer, + ts->dest, PROXY_HTTP_V3, (bool)ts->udp); if(!result) result = Curl_creader_set_null(data); if(!result) From 58df614c86bf888e553429febeddcd4e0845070b Mon Sep 17 00:00:00 2001 From: Viktor Szakats Date: Thu, 10 Sep 2026 21:19:47 +0200 Subject: [PATCH 098/481] GHA: set `cache-mode` Limit cache access to the minimum required. Refs: https://github.blog/changelog/2026-09-10-control-github-actions-cache-access-with-cache-mode/ https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-syntax#cache-mode https://docs.github.com/en/actions/reference/workflows-and-actions/dependency-caching#bypassing-the-default-untrusted-trigger-cache-restriction Closes #22904 --- .github/workflows/appveyor-status.yml | 1 + .github/workflows/checkdocs.yml | 1 + .github/workflows/checksrc.yml | 2 ++ .github/workflows/checkurls.yml | 1 + .github/workflows/codeql.yml | 1 + .github/workflows/configure-vs-cmake.yml | 1 + .github/workflows/curl-for-win.yml | 1 + .github/workflows/distcheck.yml | 1 + .github/workflows/fuzz.yml | 1 + .github/workflows/http3-linux.yml | 2 +- .github/workflows/label.yml | 1 + .github/workflows/linux-old.yml | 1 + .github/workflows/linux.yml | 1 + .github/workflows/macos.yml | 2 ++ .github/workflows/non-native.yml | 3 +++ .github/workflows/windows.yml | 5 ++++- 16 files changed, 23 insertions(+), 2 deletions(-) diff --git a/.github/workflows/appveyor-status.yml b/.github/workflows/appveyor-status.yml index c821771fc43a..5dc51a4b3464 100644 --- a/.github/workflows/appveyor-status.yml +++ b/.github/workflows/appveyor-status.yml @@ -12,6 +12,7 @@ concurrency: cancel-in-progress: true permissions: {} +cache-mode: none jobs: split: diff --git a/.github/workflows/checkdocs.yml b/.github/workflows/checkdocs.yml index 26397ea6b55e..c1c152fbed03 100644 --- a/.github/workflows/checkdocs.yml +++ b/.github/workflows/checkdocs.yml @@ -35,6 +35,7 @@ concurrency: cancel-in-progress: true permissions: {} +cache-mode: none env: DO_NOT_TRACK: '1' diff --git a/.github/workflows/checksrc.yml b/.github/workflows/checksrc.yml index c7b65a31d351..5b199df1c246 100644 --- a/.github/workflows/checksrc.yml +++ b/.github/workflows/checksrc.yml @@ -30,6 +30,7 @@ concurrency: cancel-in-progress: true permissions: {} +cache-mode: none env: DO_NOT_TRACK: '1' @@ -233,6 +234,7 @@ jobs: --ignore ubuntu-26.04 \ --ignore ubuntu-24.04-firewall \ --ignore 'unexpected key "background"' \ + --ignore 'unexpected key "cache-mode"' \ --ignore 'unexpected key "wait"' \ --ignore 'unexpected key "wait-all"' \ --ignore 'must run script with "run" section' \ diff --git a/.github/workflows/checkurls.yml b/.github/workflows/checkurls.yml index 1842b975f42b..7a1de25ef26b 100644 --- a/.github/workflows/checkurls.yml +++ b/.github/workflows/checkurls.yml @@ -22,6 +22,7 @@ concurrency: cancel-in-progress: true permissions: {} +cache-mode: none env: DO_NOT_TRACK: '1' diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 18a2dd27f9f7..b5afa5dc1846 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -34,6 +34,7 @@ concurrency: cancel-in-progress: true permissions: {} +cache-mode: none env: DO_NOT_TRACK: '1' diff --git a/.github/workflows/configure-vs-cmake.yml b/.github/workflows/configure-vs-cmake.yml index 73dfabb6d9dc..403c021a7b01 100644 --- a/.github/workflows/configure-vs-cmake.yml +++ b/.github/workflows/configure-vs-cmake.yml @@ -37,6 +37,7 @@ concurrency: cancel-in-progress: true permissions: {} +cache-mode: none env: CURL_CI: github diff --git a/.github/workflows/curl-for-win.yml b/.github/workflows/curl-for-win.yml index 39f6d3344de1..69238b45c81b 100644 --- a/.github/workflows/curl-for-win.yml +++ b/.github/workflows/curl-for-win.yml @@ -32,6 +32,7 @@ concurrency: cancel-in-progress: true permissions: {} +cache-mode: none env: CW_NOGET: 'curl trurl' diff --git a/.github/workflows/distcheck.yml b/.github/workflows/distcheck.yml index 65bef132dac0..178c9b642e61 100644 --- a/.github/workflows/distcheck.yml +++ b/.github/workflows/distcheck.yml @@ -20,6 +20,7 @@ concurrency: cancel-in-progress: true permissions: {} +cache-mode: none env: CURL_TEST_MIN: 1500 diff --git a/.github/workflows/fuzz.yml b/.github/workflows/fuzz.yml index 7ac58d902b2a..374f597adbea 100644 --- a/.github/workflows/fuzz.yml +++ b/.github/workflows/fuzz.yml @@ -39,6 +39,7 @@ concurrency: cancel-in-progress: true permissions: {} +cache-mode: write env: DO_NOT_TRACK: '1' diff --git a/.github/workflows/http3-linux.yml b/.github/workflows/http3-linux.yml index c33dca342419..69d7061bd4be 100644 --- a/.github/workflows/http3-linux.yml +++ b/.github/workflows/http3-linux.yml @@ -32,6 +32,7 @@ concurrency: cancel-in-progress: true permissions: {} +cache-mode: write env: MAKEFLAGS: -j 5 @@ -71,7 +72,6 @@ jobs: build-cache: name: 'Build caches' runs-on: ubuntu-26.04 - steps: - name: 'cache awslc' uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 diff --git a/.github/workflows/label.yml b/.github/workflows/label.yml index 9ef543db2f5c..7e314c19ba27 100644 --- a/.github/workflows/label.yml +++ b/.github/workflows/label.yml @@ -17,6 +17,7 @@ concurrency: cancel-in-progress: true permissions: {} +cache-mode: none env: DO_NOT_TRACK: '1' diff --git a/.github/workflows/linux-old.yml b/.github/workflows/linux-old.yml index 7fe01f69b436..510534caa0c1 100644 --- a/.github/workflows/linux-old.yml +++ b/.github/workflows/linux-old.yml @@ -46,6 +46,7 @@ concurrency: cancel-in-progress: true permissions: {} +cache-mode: none env: MAKEFLAGS: -j 5 diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 7d7396dee649..ee2ebe47ffad 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -30,6 +30,7 @@ concurrency: cancel-in-progress: true permissions: {} +cache-mode: write env: MAKEFLAGS: -j 5 diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index a26dc6781a0c..a2037e725a07 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -32,6 +32,7 @@ concurrency: cancel-in-progress: true permissions: {} +cache-mode: none # Apple APIs and the macos-version-min value required to avoid deprecation # warnings with llvm/clang, and/or the feature getting enabled at build-time @@ -56,6 +57,7 @@ jobs: name: "iOS, ${{ (matrix.build.generator && format('CM-{0}', matrix.build.generator)) || (matrix.build.generate && 'CM' || 'AM' )}} ${{ matrix.build.name }} arm64" runs-on: macos-latest timeout-minutes: 10 + cache-mode: write env: DEVELOPER_DIR: "/Applications/Xcode${{ matrix.build.xcode && format('_{0}', matrix.build.xcode) || '' }}.app/Contents/Developer" CC: 'clang' diff --git a/.github/workflows/non-native.yml b/.github/workflows/non-native.yml index 3e0ec7129f50..7cd795ec1642 100644 --- a/.github/workflows/non-native.yml +++ b/.github/workflows/non-native.yml @@ -32,6 +32,7 @@ concurrency: cancel-in-progress: true permissions: {} +cache-mode: none env: CURL_CI: github @@ -257,6 +258,7 @@ jobs: name: "AmigaOS, ${{ matrix.build == 'cmake' && 'CM' || 'AM' }} gcc AmiSSL m68k" runs-on: ubuntu-latest timeout-minutes: 5 + cache-mode: write env: MAKEFLAGS: -j 5 MATRIX_BUILD: '${{ matrix.build }}' @@ -477,6 +479,7 @@ jobs: name: "MS-DOS, ${{ matrix.build == 'cmake' && 'CM' || 'AM' }} djgpp !ssl i586" runs-on: ubuntu-latest timeout-minutes: 5 + cache-mode: write env: LDFLAGS: -s MAKEFLAGS: -j 5 diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index e625a5e68a49..11703522bbec 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -32,6 +32,7 @@ concurrency: cancel-in-progress: true permissions: {} +cache-mode: write env: CURL_CI: github @@ -58,7 +59,7 @@ jobs: with: path: C:\my-stunnel key: ${{ runner.os }}-stunnel-${{ env.STUNNEL_VERSION }}-amd64 - lookup-only: true + lookup-only: true # requires cache read permission - name: 'install test prereqs (stunnel)' if: ${{ !steps.cache-stunnel.outputs.cache-hit }} @@ -75,6 +76,7 @@ jobs: needs: build-cache runs-on: windows-2022 timeout-minutes: 10 + cache-mode: read defaults: run: shell: D:\cygwin\bin\bash.exe '{0}' # zizmor: ignore[misfeature] @@ -803,6 +805,7 @@ jobs: name: "linux-mingw, ${{ matrix.build == 'cmake' && 'CM' || 'AM' }} ${{ matrix.compiler }}" runs-on: ubuntu-26.04 timeout-minutes: 10 + cache-mode: none env: LDFLAGS: -s MAKEFLAGS: -j 5 From 2db7d4d6ea6c2f58c1a545acac43db61b21ff9c6 Mon Sep 17 00:00:00 2001 From: Daniel Stenberg Date: Thu, 10 Sep 2026 22:27:22 +0200 Subject: [PATCH 099/481] lib: make private functions static and remove unused code - multi: make multi_xfers_running() static - hash: made hash_str() static - splay: make splay() static - splay: make Curl_splayget() present only for unittests - splay: make splaygetbest() static - splay: make splayinsert() static - splay: make splayremove() static - splay: remove Curl_splayset(): not used - uint-hashset: make u8_strset_set() static - llist: make llist_insert_next() static - sendf: remove Curl_creader_resume_from(), not used anymore Closes #22906 --- docs/internals/LLIST.md | 16 ++-- docs/internals/SPLAY.md | 41 ++++------- lib/hash.c | 36 +++++---- lib/hash.h | 1 - lib/llist.c | 15 ++-- lib/llist.h | 2 - lib/multi.c | 14 ++-- lib/multiif.h | 3 - lib/sendf.c | 8 -- lib/sendf.h | 14 ---- lib/splay.c | 157 +++++++++++++++++++++------------------- lib/splay.h | 20 +---- lib/uint-hashset.c | 12 ++- lib/uint-hashset.h | 5 +- tests/unit/unit1300.c | 22 +++--- tests/unit/unit1309.c | 22 +++--- tests/unit/unit1603.c | 8 +- tests/unit/unit3211.c | 12 +-- 18 files changed, 191 insertions(+), 217 deletions(-) diff --git a/docs/internals/LLIST.md b/docs/internals/LLIST.md index b77e86598f45..8daf58a36d4a 100644 --- a/docs/internals/LLIST.md +++ b/docs/internals/LLIST.md @@ -28,7 +28,7 @@ list. There are two functions for adding a node to a linked list: 1. Add it last in the list with `Curl_llist_append` -2. Add it after a specific existing node with `Curl_llist_insert_next` +2. Add it after a specific existing node with `llist_insert_next` When a node is added to a list, it stores an associated custom pointer to anything you like and you provide a pointer to a `struct Curl_llist_node` @@ -60,7 +60,7 @@ it: /* add the first struct to the list */ Curl_llist_append(&barlist, &entries[0], &entries[0].storage); -See also `Curl_llist_insert_next`. +See also `llist_insert_next`. ## Remove a node @@ -134,17 +134,17 @@ Adds `node` last in the `list` with a custom pointer to `elem`. The function is infallible. -## `Curl_llist_insert_next` +## `llist_insert_next` ~~~c -void Curl_llist_insert_next(struct Curl_llist *list, - struct Curl_llist_node *node, - const void *elem, - struct Curl_llist_node *node); +void llist_insert_next(struct Curl_llist *list, + struct Curl_llist_node *before, + const void *elem, + struct Curl_llist_node *node); ~~~ Adds `node` to the `list` with a custom pointer to `elem` immediately after -the previous list `node`. +the previous list `before`. The function is infallible. diff --git a/docs/internals/SPLAY.md b/docs/internals/SPLAY.md index 4a9e49940c5e..cb7537487426 100644 --- a/docs/internals/SPLAY.md +++ b/docs/internals/SPLAY.md @@ -45,21 +45,21 @@ multi handle carries a "base timestamp", set once when created, and the timeout values are calculated relative to that. This works for about 500,000 years of continued operation of a multi handle. -## `Curl_splay` +## `splay` ~~~c -struct Curl_tree *Curl_splay(struct curltime i, struct Curl_tree *t); +struct Curl_tree *splay(timediff_t key, struct Curl_tree *t); ~~~ -Rearranges the tree `t` after the provide time `i`. +Rearranges the tree `t` after the provide time `key`. -## `Curl_splayinsert` +## `splayinsert` ~~~c -struct Curl_tree *Curl_splayinsert(timediff_t key, - struct Curl_tree *t, - struct Curl_tree *node, - uint32_t id); +struct Curl_tree *splayinsert(timediff_t key, + struct Curl_tree *t, + struct Curl_tree *node, + uint32_t id); ~~~ This function inserts a new `node` in the tree, using the given `key` @@ -72,12 +72,12 @@ has that arranged. It returns a pointer to the new tree root. -## `Curl_splaygetbest` +## `splaygetbest` ~~~c -struct Curl_tree *Curl_splaygetbest(timediff_t key, - struct Curl_tree *tree, - struct Curl_tree **removed); +struct Curl_tree *splaygetbest(timediff_t key, + struct Curl_tree *tree, + struct Curl_tree **removed); ~~~ If there is a node in the `tree` that has a timeout that is less than the @@ -86,12 +86,12 @@ in the `*removed` pointer (or NULL if there was no match). It returns a pointer to the new tree root. -## `Curl_splayremove` +## `splayremove` ~~~c -int Curl_splayremove(struct Curl_tree *tree, - struct Curl_tree *node, - struct Curl_tree **newroot); +int splayremove(struct Curl_tree *tree, + struct Curl_tree *node, + struct Curl_tree **newroot); ~~~ Removes a given `node` from a splay `tree`, and returns the `newroot` @@ -99,15 +99,6 @@ identifying the new tree root. Note that a clean tree without any nodes present implies a NULL pointer. -## `Curl_splayset` - -~~~c -void Curl_splayset(struct Curl_tree *node, uint32_t id); -~~~ - -Sets the `id` in the splay node. This value is not used -by the splay code itself and can be retrieved again with `Curl_splayget`. - ## `Curl_splayget` ~~~c diff --git a/lib/hash.c b/lib/hash.c index 23883105caca..050f80771a7f 100644 --- a/lib/hash.c +++ b/lib/hash.c @@ -33,6 +33,7 @@ #define ITERINIT 0x5FEDCBA9 #endif + typedef size_t (*hash_function)(const void *key, size_t key_length, size_t slots_num); @@ -51,6 +52,24 @@ struct hash_functions { comp_function compare; }; +/* @unittest 1603 + */ +UNITTEST size_t hash_str(const void *key, size_t key_length, size_t slots_num); +UNITTEST size_t hash_str(const void *key, size_t key_length, size_t slots_num) +{ + const char *key_str = (const char *)key; + const char *end = key_str + key_length; + size_t h = 5381; + + while(key_str < end) { + size_t j = (size_t)*key_str++; + h += h << 5; + h ^= j; + } + + return (h % slots_num); +} + /* Avoid treating the variable-length key[1] as a one-byte object. */ static char *hash_elem_key(struct Curl_hash_element *he) { @@ -89,7 +108,7 @@ static size_t compare_bytes(const void *key1, size_t key1_len, } static const struct hash_functions hash_functions[] = { - { Curl_hash_str, compare_bytes }, + { hash_str, compare_bytes }, { hash_socket, compare_socket } }; @@ -409,21 +428,6 @@ void Curl_hash_clean_with_criterium(struct Curl_hash *h, void *user, } } -size_t Curl_hash_str(const void *key, size_t key_length, size_t slots_num) -{ - const char *key_str = (const char *)key; - const char *end = key_str + key_length; - size_t h = 5381; - - while(key_str < end) { - size_t j = (size_t)*key_str++; - h += h << 5; - h ^= j; - } - - return (h % slots_num); -} - void Curl_hash_start_iterate(struct Curl_hash *hash, struct Curl_hash_iterator *iter) { diff --git a/lib/hash.h b/lib/hash.h index 02466d827e29..c889131c687b 100644 --- a/lib/hash.h +++ b/lib/hash.h @@ -83,7 +83,6 @@ size_t Curl_hash_count(struct Curl_hash *h); void Curl_hash_clean(struct Curl_hash *h); void Curl_hash_clean_with_criterium(struct Curl_hash *h, void *user, int (*comp)(void *, void *)); -size_t Curl_hash_str(const void *key, size_t key_length, size_t slots_num); void Curl_hash_start_iterate(struct Curl_hash *hash, struct Curl_hash_iterator *iter); struct Curl_hash_element *Curl_hash_next_element( diff --git a/lib/llist.c b/lib/llist.c index 6528ebaef2c3..516b15673b9c 100644 --- a/lib/llist.c +++ b/lib/llist.c @@ -54,7 +54,7 @@ void Curl_llist_init(struct Curl_llist *l, Curl_llist_dtor dtor) } /* - * Curl_llist_insert_next() + * llist_insert_next() * * Inserts a new list element after the given one 'e'. If the given existing * entry is NULL and the list already has elements, the new one will be @@ -64,10 +64,13 @@ void Curl_llist_init(struct Curl_llist *l, Curl_llist_dtor dtor) * * @unittest 1300 */ -void Curl_llist_insert_next(struct Curl_llist *list, - struct Curl_llist_node *e, /* may be NULL */ - const void *p, - struct Curl_llist_node *ne) +UNITTEST void llist_insert_next(struct Curl_llist *list, + struct Curl_llist_node *e, const void *p, + struct Curl_llist_node *ne); +UNITTEST void llist_insert_next(struct Curl_llist *list, + struct Curl_llist_node *e, /* may be NULL */ + const void *p, + struct Curl_llist_node *ne) { DEBUGASSERT(list); DEBUGASSERT(list->_init == LLISTINIT); @@ -120,7 +123,7 @@ void Curl_llist_append(struct Curl_llist *list, const void *p, DEBUGASSERT(list); DEBUGASSERT(list->_init == LLISTINIT); DEBUGASSERT(ne); - Curl_llist_insert_next(list, list->_tail, p, ne); + llist_insert_next(list, list->_tail, p, ne); } void *Curl_node_take_elem(struct Curl_llist_node *e) diff --git a/lib/llist.h b/lib/llist.h index de4adc972f47..38fea5c208ef 100644 --- a/lib/llist.h +++ b/lib/llist.h @@ -51,8 +51,6 @@ struct Curl_llist_node { }; void Curl_llist_init(struct Curl_llist *l, Curl_llist_dtor dtor); -void Curl_llist_insert_next(struct Curl_llist *list, struct Curl_llist_node *e, - const void *p, struct Curl_llist_node *ne); void Curl_llist_append(struct Curl_llist *list, const void *p, struct Curl_llist_node *ne); void Curl_node_remove(struct Curl_llist_node *e); diff --git a/lib/multi.c b/lib/multi.c index d51e68b7803d..a6ee80edb738 100644 --- a/lib/multi.c +++ b/lib/multi.c @@ -83,6 +83,8 @@ static void multi_xfer_bufs_free(struct Curl_multi *multi); #ifdef DEBUGBUILD static void multi_xfer_tbl_dump(struct Curl_multi *multi); #endif +/* Get the # of transfers current in process/pending. */ +static uint32_t multi_xfers_running(struct Curl_multi *multi); static const struct curltime *multi_now(struct Curl_multi *multi) { @@ -555,7 +557,7 @@ CURLMcode Curl_multi_add_handle(struct Curl_multi *multi, multi->admin->set.no_signal = data->set.no_signal; CURL_TRC_M(data, "added to multi, mid=%u, running=%u, total=%u", - data->mid, Curl_multi_xfers_running(multi), + data->mid, multi_xfers_running(multi), Curl_uint32_tbl_count(&multi->xfers)); return CURLM_OK; } @@ -875,7 +877,7 @@ CURLMcode Curl_multi_remove_handle(struct Curl_multi *multi, } CURL_TRC_M(data, "removed from multi, mid=%u, running=%u, total=%u", - mid, Curl_multi_xfers_running(multi), + mid, multi_xfers_running(multi), Curl_uint32_tbl_count(&multi->xfers)); return CURLM_OK; } @@ -2898,7 +2900,7 @@ static CURLMcode multi_perform(struct Curl_multi *multi, if(Curl_uint32_bset_first(&multi->process, &mid)) { CURL_TRC_M(multi->admin, "multi_perform(running=%u)", - Curl_multi_xfers_running(multi)); + multi_xfers_running(multi)); do { struct Curl_easy *data = Curl_multi_get_easy(multi, mid); CURLMcode mresult; @@ -2951,7 +2953,7 @@ static CURLMcode multi_perform(struct Curl_multi *multi, } if(running_handles) { - uint32_t running = Curl_multi_xfers_running(multi); + uint32_t running = multi_xfers_running(multi); *running_handles = (running < INT_MAX) ? (int)running : INT_MAX; } @@ -3300,7 +3302,7 @@ static CURLMcode multi_socket(struct Curl_multi *multi, mresult = Curl_mntfy_dispatch_all(multi); if(running_handles) { - uint32_t running = Curl_multi_xfers_running(multi); + uint32_t running = multi_xfers_running(multi); *running_handles = (running < INT_MAX) ? (int)running : INT_MAX; } @@ -4160,7 +4162,7 @@ bool Curl_multi_knows_easy(struct Curl_multi *multi, struct Curl_easy *data) return Curl_uint32_tbl_get(&multi->xfers, data->mid) == data; } -uint32_t Curl_multi_xfers_running(struct Curl_multi *multi) +static uint32_t multi_xfers_running(struct Curl_multi *multi) { if(!multi) { DEBUGASSERT(0); diff --git a/lib/multiif.h b/lib/multiif.h index f36a3fec618a..30b453e7589a 100644 --- a/lib/multiif.h +++ b/lib/multiif.h @@ -164,9 +164,6 @@ bool Curl_multi_knows_easy(struct Curl_multi *multi, struct Curl_easy *data); * admin handle. */ uint32_t Curl_multi_xfers_attached(struct Curl_multi *multi); -/* Get the # of transfers current in process/pending. */ -uint32_t Curl_multi_xfers_running(struct Curl_multi *multi); - /* Mark a transfer as dirty, e.g. to be rerun at earliest convenience. * A cheap operation, can be done many times repeatedly. */ void Curl_multi_mark_dirty(struct Curl_easy *data); diff --git a/lib/sendf.c b/lib/sendf.c index abd2e3fdbfba..d6ff92500fb6 100644 --- a/lib/sendf.c +++ b/lib/sendf.c @@ -1446,14 +1446,6 @@ curl_off_t Curl_creader_client_length(struct Curl_easy *data) return r ? r->crt->total_length(data, r) : -1; } -CURLcode Curl_creader_resume_from(struct Curl_easy *data, curl_off_t offset) -{ - struct Curl_creader *r = data->req.reader.stack; - while(r && r->phase != CURL_CR_CLIENT) - r = r->next; - return r ? r->crt->resume_from(data, r, offset) : CURLE_READ_ERROR; -} - CURLcode Curl_creader_unpause(struct Curl_easy *data) { struct Curl_creader *reader = data->req.reader.stack; diff --git a/lib/sendf.h b/lib/sendf.h index 9863c5b0dcaa..39c7a8aaec5b 100644 --- a/lib/sendf.h +++ b/lib/sendf.h @@ -376,20 +376,6 @@ curl_off_t Curl_creader_total_length(struct Curl_easy *data); */ curl_off_t Curl_creader_client_length(struct Curl_easy *data); -/** - * Ask the installed reader at phase CURL_CR_CLIENT to start - * reading from the given offset. On success, this will reduce - * the `total_length()` by the amount. - * @param data the transfer to read client bytes for - * @param offset the offset where to start reads from, negative - * values will be ignored. - * @return CURLE_OK if offset could be set - * CURLE_READ_ERROR if not supported by reader or seek/read failed - * of offset larger than total length - * CURLE_PARTIAL_FILE if offset led to 0 total length - */ -CURLcode Curl_creader_resume_from(struct Curl_easy *data, curl_off_t offset); - /** * Unpause all installed readers. */ diff --git a/lib/splay.c b/lib/splay.c index e78317fbd368..df3734d53c5b 100644 --- a/lib/splay.c +++ b/lib/splay.c @@ -26,6 +26,17 @@ #include "urldata.h" #include "splay.h" +static struct Curl_tree *splay(timediff_t key, + struct Curl_tree *root); + +#ifdef UNITTESTS +uint32_t Curl_splayget(struct Curl_tree *node) +{ + DEBUGASSERT(node); + return node->id; +} + +#endif void Curl_timeouts_init(struct Curl_timeouts *timeouts, const struct curltime *ptime_base) @@ -56,7 +67,7 @@ int Curl_timeouts_next_ms(struct Curl_timeouts *timeouts, uint32_t *pmid) { if(timeouts->tree) { /* splay the lowest key to the root */ - timeouts->tree = Curl_splay(TIMEDIFF_T_MIN, timeouts->tree); + timeouts->tree = splay(TIMEDIFF_T_MIN, timeouts->tree); } if(timeouts->tree) { @@ -80,50 +91,6 @@ int Curl_timeouts_next_ms(struct Curl_timeouts *timeouts, return -1; } -bool Curl_timeouts_remove_expired(struct Curl_timeouts *timeouts, - const struct curltime *ts, - uint32_t *pmid) -{ - if(timeouts->tree) { - struct Curl_tree *t = NULL; - timediff_t elapsed_us = Curl_timeouts_offset_us(timeouts, ts); - timeouts->tree = Curl_splaygetbest(elapsed_us, timeouts->tree, &t); - if(t) { - *pmid = t->id; - return TRUE; - } - } - *pmid = UINT32_MAX; - return FALSE; -} - -void Curl_timeouts_add(struct Curl_timeouts *timeouts, - struct Curl_easy *data, - timediff_t offset_us) -{ - struct Curl_tree *node = &data->state.timeouts.splaynode; - DEBUGASSERT(!node->registered); - timeouts->tree = Curl_splayinsert(offset_us, timeouts->tree, - node, data->mid); -} - -bool Curl_timeouts_remove(struct Curl_timeouts *timeouts, - struct Curl_easy *data) -{ - struct Curl_tree *node = &data->state.timeouts.splaynode; - if(node->registered) { - int rc = Curl_splayremove(timeouts->tree, node, &timeouts->tree); -#ifdef DEBUGBUILD - if(rc) - curl_mfprintf(stderr, "Internal error removing splay node = %d\n", rc); -#else - (void)rc; -#endif - return TRUE; - } - return FALSE; -} - /* * Splay using the key i (which may or may not be in the tree). * This rotates the tree, so: @@ -132,7 +99,7 @@ bool Curl_timeouts_remove(struct Curl_timeouts *timeouts, * - root->key may equal `key` or not * */ -struct Curl_tree *Curl_splay(timediff_t key, +struct Curl_tree *splay(timediff_t key, struct Curl_tree *root) { struct Curl_tree N, *l, *r, *y; @@ -203,12 +170,16 @@ struct Curl_tree *Curl_splay(timediff_t key, /* Insert key i into the tree t. Return a pointer to the resulting tree or * NULL if something went wrong. * - * @unittest: 1309 + * @unittest 1309 */ -struct Curl_tree *Curl_splayinsert(timediff_t key, - struct Curl_tree *root, - struct Curl_tree *node, - uint32_t id) +UNITTEST struct Curl_tree *splayinsert(timediff_t key, + struct Curl_tree *root, + struct Curl_tree *node, + uint32_t id); +UNITTEST struct Curl_tree *splayinsert(timediff_t key, + struct Curl_tree *root, + struct Curl_tree *node, + uint32_t id) { DEBUGASSERT(node); @@ -217,7 +188,7 @@ struct Curl_tree *Curl_splayinsert(timediff_t key, node->same = NULL; node->registered = TRUE; if(root) { - root = Curl_splay(key, root); + root = splay(key, root); DEBUGASSERT(root); if(key == root->key) { /* There already exists a node in the tree with the same key. @@ -248,12 +219,28 @@ struct Curl_tree *Curl_splayinsert(timediff_t key, return node; } +void Curl_timeouts_add(struct Curl_timeouts *timeouts, + struct Curl_easy *data, + timediff_t offset_us) +{ + struct Curl_tree *node = &data->state.timeouts.splaynode; + DEBUGASSERT(!node->registered); + timeouts->tree = splayinsert(offset_us, timeouts->tree, + node, data->mid); +} + /* Finds and deletes the best-fit node from the tree. Return a pointer to the resulting tree. best-fit means the smallest node if it is not larger than - the key */ -struct Curl_tree *Curl_splaygetbest(timediff_t key, - struct Curl_tree *root, - struct Curl_tree **removed) + the key + + @unittest 1309 +*/ +UNITTEST struct Curl_tree *splaygetbest(timediff_t key, + struct Curl_tree *root, + struct Curl_tree **removed); +UNITTEST struct Curl_tree *splaygetbest(timediff_t key, + struct Curl_tree *root, + struct Curl_tree **removed) { struct Curl_tree *x; @@ -263,7 +250,7 @@ struct Curl_tree *Curl_splaygetbest(timediff_t key, } /* find smallest */ - root = Curl_splay(TIMEDIFF_T_MIN, root); + root = splay(TIMEDIFF_T_MIN, root); DEBUGASSERT(root); if(key < root->key) { /* even the smallest is too big */ @@ -292,6 +279,23 @@ struct Curl_tree *Curl_splaygetbest(timediff_t key, return x; } +bool Curl_timeouts_remove_expired(struct Curl_timeouts *timeouts, + const struct curltime *ts, + uint32_t *pmid) +{ + if(timeouts->tree) { + struct Curl_tree *t = NULL; + timediff_t elapsed_us = Curl_timeouts_offset_us(timeouts, ts); + timeouts->tree = splaygetbest(elapsed_us, timeouts->tree, &t); + if(t) { + *pmid = t->id; + return TRUE; + } + } + *pmid = UINT32_MAX; + return FALSE; +} + /* Deletes the node we point out from the tree if it is there. Stores a * pointer to the new resulting tree in 'newroot'. * @@ -301,11 +305,14 @@ struct Curl_tree *Curl_splaygetbest(timediff_t key, * NOTE: when the last node of the tree is removed, there is no tree left so * 'newroot' will be made to point to NULL. * - * @unittest: 1309 + * @unittest 1309 */ -int Curl_splayremove(struct Curl_tree *root, - struct Curl_tree *removenode, - struct Curl_tree **newroot) +UNITTEST int splayremove(struct Curl_tree *root, + struct Curl_tree *removenode, + struct Curl_tree **newroot); +UNITTEST int splayremove(struct Curl_tree *root, + struct Curl_tree *removenode, + struct Curl_tree **newroot) { struct Curl_tree *x; @@ -316,7 +323,7 @@ int Curl_splayremove(struct Curl_tree *root, if(!removenode->registered) return 2; - root = Curl_splay(removenode->key, root); + root = splay(removenode->key, root); DEBUGASSERT(root); /* First make sure that we got the same root key as the one we want @@ -357,7 +364,7 @@ int Curl_splayremove(struct Curl_tree *root, if(!root->smaller) x = root->larger; else { - x = Curl_splay(removenode->key, root->smaller); + x = splay(removenode->key, root->smaller); DEBUGASSERT(x); x->larger = root->larger; } @@ -367,15 +374,19 @@ int Curl_splayremove(struct Curl_tree *root, return 0; } -/* set and get the custom payload for this tree node */ -void Curl_splayset(struct Curl_tree *node, uint32_t id) -{ - DEBUGASSERT(node); - node->id = id; -} - -uint32_t Curl_splayget(struct Curl_tree *node) +bool Curl_timeouts_remove(struct Curl_timeouts *timeouts, + struct Curl_easy *data) { - DEBUGASSERT(node); - return node->id; + struct Curl_tree *node = &data->state.timeouts.splaynode; + if(node->registered) { + int rc = splayremove(timeouts->tree, node, &timeouts->tree); +#ifdef DEBUGBUILD + if(rc) + curl_mfprintf(stderr, "Internal error removing splay node = %d\n", rc); +#else + (void)rc; +#endif + return TRUE; + } + return FALSE; } diff --git a/lib/splay.h b/lib/splay.h index f29cb0077b7a..5731d810cce4 100644 --- a/lib/splay.h +++ b/lib/splay.h @@ -69,24 +69,8 @@ void Curl_timeouts_add(struct Curl_timeouts *timeouts, bool Curl_timeouts_remove(struct Curl_timeouts *timeouts, struct Curl_easy *data); -struct Curl_tree *Curl_splay(timediff_t key, - struct Curl_tree *root); - -struct Curl_tree *Curl_splayinsert(timediff_t key, - struct Curl_tree *root, - struct Curl_tree *node, - uint32_t id); - -struct Curl_tree *Curl_splaygetbest(timediff_t key, - struct Curl_tree *root, - struct Curl_tree **removed); - -int Curl_splayremove(struct Curl_tree *root, - struct Curl_tree *removenode, - struct Curl_tree **newroot); - -/* set and get the custom payload for this tree node */ -void Curl_splayset(struct Curl_tree *node, uint32_t id); +#ifdef UNITTESTS uint32_t Curl_splayget(struct Curl_tree *node); +#endif #endif /* HEADER_CURL_SPLAY_H */ diff --git a/lib/uint-hashset.c b/lib/uint-hashset.c index 84e9e5ec7bef..6f2fbd5fb594 100644 --- a/lib/uint-hashset.c +++ b/lib/uint-hashset.c @@ -193,10 +193,13 @@ static bool u8_strset_get_index(struct u8_strset *set, return FALSE; } +#ifdef UNITTESTS +/* @unittest 3211 */ uint16_t Curl_u8_strset_count(struct u8_strset *set) { return set->count; } +#endif const char *Curl_u8_strset_get(struct u8_strset *set, uint8_t id) { @@ -254,8 +257,11 @@ CURLcode Curl_u8_strset_setx(struct u8_strset *set, return Curl_u8_strset_setn(set, id, val); } -CURLcode Curl_u8_strset_set(struct u8_strset *set, - uint8_t id, const char *str) +/* @unittest 3211 */ +UNITTEST CURLcode u8_strset_set(struct u8_strset *set, + uint8_t id, const char *str); +UNITTEST CURLcode u8_strset_set(struct u8_strset *set, + uint8_t id, const char *str) { return Curl_u8_strset_setx(set, id, str, str ? strlen(str) : 0); } @@ -305,7 +311,7 @@ CURLcode Curl_u8_strset_copy(struct u8_strset *dest, struct u8_strset *src) Curl_u8_strset_clear(dest); for(i = 0; !result && (i < CURL_U8_SET_SLOT_CNT(src)); ++i) { if(src->data[i]) - result = Curl_u8_strset_set(dest, src->ids[i], src->data[i]); + result = u8_strset_set(dest, src->ids[i], src->data[i]); } return result; } diff --git a/lib/uint-hashset.h b/lib/uint-hashset.h index e5a1e02f00a8..04352ac7c2f7 100644 --- a/lib/uint-hashset.h +++ b/lib/uint-hashset.h @@ -61,12 +61,13 @@ struct u8_strset { void Curl_u8_strset_init(struct u8_strset *set); void Curl_u8_strset_clear(struct u8_strset *set); +#ifdef UNITTESTS uint16_t Curl_u8_strset_count(struct u8_strset *set); +#endif + const char *Curl_u8_strset_get(struct u8_strset *set, uint8_t id); /* Set string for id, makes a copy. */ -CURLcode Curl_u8_strset_set(struct u8_strset *set, - uint8_t id, const char *str); CURLcode Curl_u8_strset_setx(struct u8_strset *set, uint8_t id, const char *str, size_t slen); /* Set string for id, takes ownership of `str` even on failure. */ diff --git a/tests/unit/unit1300.c b/tests/unit/unit1300.c index 7f0ec0d0c74d..bd7be156bda7 100644 --- a/tests/unit/unit1300.c +++ b/tests/unit/unit1300.c @@ -73,7 +73,7 @@ static CURLcode test_unit1300(const char *arg) "list tail should initiate to NULL"); /** - * testing Curl_llist_insert_next + * testing llist_insert_next * case 1: * list is empty * @assumptions: @@ -82,8 +82,8 @@ static CURLcode test_unit1300(const char *arg) * 3: list tail is the same as list head */ - Curl_llist_insert_next(&llist, Curl_llist_head(&llist), &unusedData_case1, - &case1_list); + llist_insert_next(&llist, Curl_llist_head(&llist), &unusedData_case1, + &case1_list); fail_unless(Curl_llist_count(&llist) == 1, "List size should be 1 after adding a new element"); @@ -95,7 +95,7 @@ static CURLcode test_unit1300(const char *arg) "tail and head should be the same"); /** - * testing Curl_llist_insert_next + * testing llist_insert_next * case 2: * list has 1 element, adding one element after the head * @assumptions: @@ -103,8 +103,8 @@ static CURLcode test_unit1300(const char *arg) * 2: the list tail should be our newly created element */ - Curl_llist_insert_next(&llist, Curl_llist_head(&llist), - &unusedData_case3, &case3_list); + llist_insert_next(&llist, Curl_llist_head(&llist), + &unusedData_case3, &case3_list); fail_unless(Curl_node_elem(Curl_node_next(Curl_llist_head(&llist))) == &unusedData_case3, "the node next to head is not getting set correctly"); @@ -112,7 +112,7 @@ static CURLcode test_unit1300(const char *arg) "the list tail is not getting set correctly"); /** - * testing Curl_llist_insert_next + * testing llist_insert_next * case 3: * list has >1 element, adding one element after "NULL" * @assumptions: @@ -120,8 +120,8 @@ static CURLcode test_unit1300(const char *arg) * 2: the list tail should different from newly created element */ - Curl_llist_insert_next(&llist, Curl_llist_head(&llist), - &unusedData_case2, &case2_list); + llist_insert_next(&llist, Curl_llist_head(&llist), + &unusedData_case2, &case2_list); fail_unless(Curl_node_elem(Curl_node_next(Curl_llist_head(&llist))) == &unusedData_case2, "the node next to head is not getting set correctly"); @@ -165,8 +165,8 @@ static CURLcode test_unit1300(const char *arg) * 2: element->previous->next is element->next * 3: element->next->previous is element->previous */ - Curl_llist_insert_next(&llist, Curl_llist_head(&llist), &unusedData_case3, - &case4_list); + llist_insert_next(&llist, Curl_llist_head(&llist), &unusedData_case3, + &case4_list); llist_size = Curl_llist_count(&llist); fail_unless(llist_size == 3, "should be 3 list members"); diff --git a/tests/unit/unit1309.c b/tests/unit/unit1309.c index 1bb81359ac2e..95034d6adf22 100644 --- a/tests/unit/unit1309.c +++ b/tests/unit/unit1309.c @@ -89,7 +89,7 @@ static void test_timeouts_nonzero_usec_base(void) "now offset did not shift by the base microseconds"); memset(&node, 0, sizeof(node)); - timeouts.tree = Curl_splayinsert(deadline_offset, timeouts.tree, &node, 42); + timeouts.tree = splayinsert(deadline_offset, timeouts.tree, &node, 42); timeout_ms = Curl_timeouts_next_ms(&timeouts, &now, &expire_offset, &mid); fail_unless(timeout_ms == 1150, "timeout changed with whole-second base"); @@ -116,7 +116,7 @@ static CURLcode test_unit1309(const char *arg) timediff_t key; key = (541 * i) % 1023; - root = Curl_splayinsert(key, root, &nodes[i], (uint32_t)key); + root = splayinsert(key, root, &nodes[i], (uint32_t)key); fail_unless(nodes[i].registered, "node should have been registered"); } @@ -129,14 +129,14 @@ static CURLcode test_unit1309(const char *arg) splayprint(root, 0, 1); curl_mprintf("remove node %d, payload %u\n", (int)rem, Curl_splayget(&nodes[rem])); - rc = Curl_splayremove(root, &nodes[rem], &root); + rc = splayremove(root, &nodes[rem], &root); if(rc) { /* failed! */ curl_mprintf("remove %d failed!\n", (int)rem); fail("remove"); } fail_unless(!nodes[rem].registered, "node should not be registered"); - rc = Curl_splayremove(root, &nodes[rem], &root); + rc = splayremove(root, &nodes[rem], &root); if(!rc) { /* failed! */ curl_mprintf("double remove %d did not fail!\n", (int)rem); @@ -154,8 +154,8 @@ static CURLcode test_unit1309(const char *arg) /* add some nodes with the same key */ for(j = 0; j <= i % 3; j++) { - root = Curl_splayinsert(key, root, &nodes[(i * 3) + j], - (uint32_t)(key * 10 + j)); + root = splayinsert(key, root, &nodes[(i * 3) + j], + (uint32_t)(key * 10 + j)); } } @@ -163,12 +163,12 @@ static CURLcode test_unit1309(const char *arg) for(i = 0; i <= 1100; i += 100) { curl_mprintf("Removing nodes not larger than %d\n", (int)i); tv_now = i; - root = Curl_splaygetbest(tv_now, root, &removed); + root = splaygetbest(tv_now, root, &removed); while(removed) { curl_mprintf("removed payload %u[%u]\n", Curl_splayget(removed) / 10, Curl_splayget(removed) % 10); - root = Curl_splaygetbest(tv_now, root, &removed); + root = splaygetbest(tv_now, root, &removed); } } @@ -177,14 +177,14 @@ static CURLcode test_unit1309(const char *arg) /* rebuild tree with duplicate values */ for(i = 0; i < NUM_NODES; i++) { timediff_t key = (541 * i) % 128; - root = Curl_splayinsert(key, root, &nodes[i], (uint32_t)i); + root = splayinsert(key, root, &nodes[i], (uint32_t)i); } removed = NULL; timeout_last = -1; for(i = 0; i <= 128; i += 32) { curl_mprintf("Removing nodes not larger than %d\n", (int)i); - root = Curl_splaygetbest(i, root, &removed); + root = splaygetbest(i, root, &removed); while(removed) { curl_mprintf("removed payload %u[timeout=%d]\n", Curl_splayget(removed), (int)removed->key); @@ -195,7 +195,7 @@ static CURLcode test_unit1309(const char *arg) fail("wrong timeout order"); } timeout_last = removed->key; - root = Curl_splaygetbest(i, root, &removed); + root = splaygetbest(i, root, &removed); } } diff --git a/tests/unit/unit1603.c b/tests/unit/unit1603.c index 7de26bc82f51..c06f47116e99 100644 --- a/tests/unit/unit1603.c +++ b/tests/unit/unit1603.c @@ -125,10 +125,10 @@ static CURLcode test_unit1603(const char *arg) /* Ensure the key hashes are as expected in order to test both hash collisions and a full table. Unfortunately, the hashes can vary between architectures. */ - if(Curl_hash_str(key1, strlen(key1), slots) != 1 || - Curl_hash_str(key2, strlen(key2), slots) != 0 || - Curl_hash_str(key3, strlen(key3), slots) != 2 || - Curl_hash_str(key4, strlen(key4), slots) != 1) + if(hash_str(key1, strlen(key1), slots) != 1 || + hash_str(key2, strlen(key2), slots) != 0 || + hash_str(key3, strlen(key3), slots) != 2 || + hash_str(key4, strlen(key4), slots) != 1) curl_mfprintf(stderr, "Warning: hashes are not computed as expected on this " "architecture; test coverage is less comprehensive\n"); diff --git a/tests/unit/unit3211.c b/tests/unit/unit3211.c index 9fcbe85779a5..d3f4443eb0ad 100644 --- a/tests/unit/unit3211.c +++ b/tests/unit/unit3211.c @@ -141,11 +141,11 @@ static void t3211_check_strset1(void) Curl_u8_strset_init(&set); fail_unless(!Curl_u8_strset_count(&set), "initial strset not empty"); - result = Curl_u8_strset_set(&set, 0, "123"); + result = u8_strset_set(&set, 0, "123"); fail_unless(!result, "add1 failed"); fail_unless(Curl_u8_strset_get(&set, 0), "get failed"); fail_unless(!t3211_strcmp("123", Curl_u8_strset_get(&set, 0)), "wrong get1"); - result = Curl_u8_strset_set(&set, 0, "456"); + result = u8_strset_set(&set, 0, "456"); fail_unless(!result, "add2 failed"); fail_unless(!t3211_strcmp("456", Curl_u8_strset_get(&set, 0)), "wrong get2"); Curl_u8_strset_unset(&set, 0); @@ -155,7 +155,7 @@ static void t3211_check_strset1(void) for(i = 0; i < 8; ++i) { idx = (uint8_t)((8 * i) + 3); curl_msnprintf(buf, sizeof(buf), "str-%d", idx); - result = Curl_u8_strset_set(&set, idx, buf); + result = u8_strset_set(&set, idx, buf); fail_unless(!result, "loop4-add failed"); fail_unless(!t3211_strcmp(buf, Curl_u8_strset_get(&set, idx)), "wrong get loop4"); @@ -177,7 +177,7 @@ static void t3211_check_strset1(void) /* Add entry 2 again, check */ idx = (uint8_t)((8 * 2) + 3); curl_msnprintf(buf, sizeof(buf), "str-%d", idx); - result = Curl_u8_strset_set(&set, idx, buf); + result = u8_strset_set(&set, idx, buf); fail_unless(!result, "re-add 2 failed"); fail_unless(!t3211_strcmp(buf, Curl_u8_strset_get(&set, idx)), "wrong re-add 2 get"); @@ -192,7 +192,7 @@ static void t3211_check_strset1(void) fail_unless(Curl_u8_strset_count(&set) == 8, "wrong count pre add 5"); idx = (uint8_t)((9 * 4) + 3); curl_msnprintf(buf, sizeof(buf), "str-%d", idx); - result = Curl_u8_strset_set(&set, idx, buf); + result = u8_strset_set(&set, idx, buf); fail_unless(!result, "add4 failed"); fail_unless(!t3211_strcmp(buf, Curl_u8_strset_get(&set, idx)), "wrong get4"); @@ -210,7 +210,7 @@ static void t3211_check_strset1(void) for(j = 0; j <= UINT8_MAX; ++j) { i = (uint8_t)j; curl_msnprintf(buf, sizeof(buf), "str-%d", i); - result = Curl_u8_strset_set(&set, i, buf); + result = u8_strset_set(&set, i, buf); fail_unless(!result, "loop256-add failed"); fail_unless(!t3211_strcmp(buf, Curl_u8_strset_get(&set, i)), "wrong get loop256"); From dc055e8a9eefe65cf63cb0396668a37c0b7c474a Mon Sep 17 00:00:00 2001 From: Bobbeh Rhino Date: Tue, 8 Sep 2026 20:24:35 -0400 Subject: [PATCH 100/481] libssh2: skip repeat checkp() for already-absent hashed key types ssh_force_knownhost_key_type() called libssh2_knownhost_checkp() once per hashed known_hosts entry, and each call itself rescans the whole known_hosts set. Cache key types already confirmed absent for the connecting host so repeat entries of the same type do not each trigger another full scan. checkp() NOTFOUND for a given (host, port, type) is independent of the key bytes passed in, so this is safe. Behavior for MATCH is unchanged, and only store->typemask is read after a match, which is identical for any entry of the same type. Tests: runtests.pl 3500 1459 600, full SFTP/SCP keyword set (58/58), checksrc.pl. Closes #22905 --- lib/vssh/libssh2.c | 34 +++++++++++++++++++++++----------- 1 file changed, 23 insertions(+), 11 deletions(-) diff --git a/lib/vssh/libssh2.c b/lib/vssh/libssh2.c index 2630bdb1a21f..6afa1c223b4e 100644 --- a/lib/vssh/libssh2.c +++ b/lib/vssh/libssh2.c @@ -652,6 +652,12 @@ static CURLcode ssh_force_knownhost_key_type(struct Curl_easy *data, !CURL_EASY_STR(data, STRING_SSH_HOST_PUBLIC_KEY_SHA256)) { struct libssh2_knownhost *store = NULL; struct connectdata *conn = data->conn; + /* key types already confirmed absent for this host, so repeat hashed + entries of the same type do not each force a fresh checkp() scan + of the whole known_hosts set. one slot per possible key type (indexed + by the type shifted down) so no entry ordering can overflow it */ + bool absent_type[(LIBSSH2_KNOWNHOST_KEY_MASK >> + LIBSSH2_KNOWNHOST_KEY_SHIFT) + 1] = { FALSE }; /* lets try to find our host in the known hosts file */ while(!libssh2_knownhost_get(sshc->kh, &store, store)) { /* For non-standard ports, the name is enclosed in */ @@ -686,17 +692,23 @@ static CURLcode ssh_force_knownhost_key_type(struct Curl_easy *data, } } else if(store->key) { - int keycheck = libssh2_knownhost_checkp( - sshc->kh, conn->origin->hostname, - (conn->origin->port != PORT_SSH) ? conn->origin->port : -1, - store->key, strlen(store->key), - LIBSSH2_KNOWNHOST_TYPE_PLAIN | - LIBSSH2_KNOWNHOST_KEYENC_BASE64 | - (store->typemask & LIBSSH2_KNOWNHOST_KEY_MASK), - NULL); - if(keycheck == LIBSSH2_KNOWNHOST_CHECK_MATCH) { - found = TRUE; - break; + unsigned int type = (store->typemask & LIBSSH2_KNOWNHOST_KEY_MASK) + >> LIBSSH2_KNOWNHOST_KEY_SHIFT; + if(!absent_type[type]) { + int keycheck = libssh2_knownhost_checkp( + sshc->kh, conn->origin->hostname, + (conn->origin->port != PORT_SSH) ? conn->origin->port : -1, + store->key, strlen(store->key), + LIBSSH2_KNOWNHOST_TYPE_PLAIN | + LIBSSH2_KNOWNHOST_KEYENC_BASE64 | + (store->typemask & LIBSSH2_KNOWNHOST_KEY_MASK), + NULL); + if(keycheck == LIBSSH2_KNOWNHOST_CHECK_MATCH) { + found = TRUE; + break; + } + else if(keycheck == LIBSSH2_KNOWNHOST_CHECK_NOTFOUND) + absent_type[type] = TRUE; } } } From a15c4a7f8c1bd0586012b532f51ac5aee00bfe0b Mon Sep 17 00:00:00 2001 From: Joshua Rogers Date: Wed, 9 Sep 2026 17:04:45 +0200 Subject: [PATCH 101/481] headers: avoid O(n^2) rescans in curl_easy_nextheader() Every call rescanned the entire header list from the head and did a full name comparison against each entry just to recompute amount/ index for the one header being returned, making a sequential enumeration of N headers O(n^2). Cache the previous pick's name/origin/request/amount/index; when the next call continues from exactly that node with the same filters, reuse the cached amount and increment the index instead of rescanning. Any other pattern (restart, filter change, non-sequential prev) falls back to the original full scan, so behavior is unchanged. Closes #22898 --- lib/headers.c | 111 +++++++++++++++++++++++++++++++++++++++----- lib/headers.h | 2 + lib/urldata.h | 4 ++ tests/data/test1945 | 8 ++-- 4 files changed, 110 insertions(+), 15 deletions(-) diff --git a/lib/headers.c b/lib/headers.c index a6416b0f07b2..438eb5464ce7 100644 --- a/lib/headers.c +++ b/lib/headers.c @@ -27,6 +27,7 @@ #include "sendf.h" #include "curl_trc.h" #include "headers.h" +#include "strcase.h" #if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_HEADERS_API) @@ -139,6 +140,80 @@ CURLHcode curl_easy_header(CURL *curl, return hresult; } +struct nextheader_cache_entry { + struct Curl_header_store *header; + size_t order; +}; + +static int nextheader_compare(const void *p1, const void *p2) +{ + const struct nextheader_cache_entry *e1 = p1; + const struct nextheader_cache_entry *e2 = p2; + const struct Curl_header_store *h1 = e1->header; + const struct Curl_header_store *h2 = e2->header; + const char *n1 = h1->name; + const char *n2 = h2->name; + + while(*n1 && *n2) { + unsigned char c1 = (unsigned char)Curl_raw_toupper(*n1++); + unsigned char c2 = (unsigned char)Curl_raw_toupper(*n2++); + if(c1 != c2) + return c1 > c2 ? 1 : -1; + } + if(*n1) + return 1; + if(*n2) + return -1; + if(e1->order != e2->order) + return e1->order > e2->order ? 1 : -1; + return 0; +} + +static bool nextheader_cache_build(struct Curl_easy *data, + unsigned int origin, + int request, size_t count) +{ + struct nextheader_cache_entry *headers; + struct Curl_llist_node *e; + size_t i = 0; + size_t first; + + headers = curlx_malloc(sizeof(*headers) * count); + if(!headers) + return FALSE; + + for(e = Curl_llist_head(&data->state.httphdrs); e; + e = Curl_node_next(e)) { + struct Curl_header_store *hs = Curl_node_elem(e); + if((hs->type & origin) && (hs->request == request)) { + headers[i].header = hs; + headers[i].order = i; + i++; + } + } + + qsort(headers, i, sizeof(*headers), nextheader_compare); + for(first = 0; first < i;) { + size_t last = first + 1; + size_t index; + while((last < i) && + curl_strequal(headers[first].header->name, + headers[last].header->name)) + last++; + for(index = first; index < last; index++) { + headers[index].header->nh_amount = last - first; + headers[index].header->nh_index = index - first; + } + first = last; + } + curlx_free(headers); + + data->state.nh_origin = origin; + data->state.nh_request = request; + data->state.nh_count = count; + return TRUE; +} + /* public API */ struct curl_header *curl_easy_nextheader(CURL *curl, unsigned int origin, @@ -156,6 +231,7 @@ struct curl_header *curl_easy_nextheader(CURL *curl, struct Curl_header_store *hs; size_t amount = 0; size_t index = 0; + size_t count; if(request > data->state.requests) goto out; @@ -187,19 +263,29 @@ struct curl_header *curl_easy_nextheader(CURL *curl, goto out; hs = Curl_node_elem(pick); - - /* count number of occurrences of this name within the mask and figure out - the index for the currently selected entry */ - for(e = Curl_llist_head(&data->state.httphdrs); e; e = Curl_node_next(e)) { - struct Curl_header_store *check = Curl_node_elem(e); - if(curl_strequal(hs->name, check->name) && - (check->request == request) && - (check->type & origin)) - amount++; - if(e == pick) - index = amount - 1; + count = Curl_llist_count(&data->state.httphdrs); + + if(((data->state.nh_count == count) && + (data->state.nh_origin == origin) && + (data->state.nh_request == request)) || + nextheader_cache_build(data, origin, request, count)) { + amount = hs->nh_amount; + index = hs->nh_index; + } + else { + /* count number of occurrences of this name within the mask and figure + out the index for the currently selected entry */ + for(e = Curl_llist_head(&data->state.httphdrs); e; + e = Curl_node_next(e)) { + struct Curl_header_store *check = Curl_node_elem(e); + if(curl_strequal(hs->name, check->name) && + (check->request == request) && + (check->type & origin)) + amount++; + if(e == pick) + index = amount - 1; + } } - copy_header_external(hs, index, amount, pick, &data->state.headerout[1]); hd = &data->state.headerout[1]; @@ -316,6 +402,7 @@ CURLcode Curl_headers_push(struct Curl_easy *data, const char *header, static void headers_reset(struct Curl_easy *data) { Curl_llist_init(&data->state.httphdrs, NULL); + data->state.nh_count = 0; } struct hds_cw_collect_ctx { diff --git a/lib/headers.h b/lib/headers.h index 283179acdcc8..6b6594d528a6 100644 --- a/lib/headers.h +++ b/lib/headers.h @@ -31,6 +31,8 @@ struct Curl_header_store { struct Curl_llist_node node; char *name; /* points into 'buffer' */ char *value; /* points into 'buffer' */ + size_t nh_amount; /* cached amount for curl_easy_nextheader() */ + size_t nh_index; /* cached index for curl_easy_nextheader() */ int request; /* 0 is the first request, then 1.. 2.. */ unsigned char type; /* CURLH_* defines */ char buffer[1]; /* this is the raw header blob */ diff --git a/lib/urldata.h b/lib/urldata.h index 8161a794abd4..5e36c631e58f 100644 --- a/lib/urldata.h +++ b/lib/urldata.h @@ -594,6 +594,10 @@ struct UrlState { #endif struct Curl_llist httphdrs; /* received headers */ struct curl_header headerout[2]; /* for external purposes */ + /* curl_easy_nextheader() cache parameters */ + unsigned int nh_origin; + int nh_request; + size_t nh_count; /* httphdrs count when the cache was filled */ #endif #ifndef CURL_DISABLE_COOKIES struct curl_slist *cookielist; /* list of cookie files set by diff --git a/tests/data/test1945 b/tests/data/test1945 index f085007c6b5d..e788528ba997 100644 --- a/tests/data/test1945 +++ b/tests/data/test1945 @@ -18,6 +18,7 @@ Content-Length: 0 Set-Cookie: onecookie=data; Set-Cookie: secondcookie=2data; Set-Cookie: cookie3=data3; +set-cookie: lowercase=data4; Location: /%TESTNUMBER0002 @@ -68,9 +69,10 @@ Proxy-Connection: Keep-Alive Server == test with trailing space (1/2) Content-Type == text/html (0/1) Content-Length == 0 (0/1) - Set-Cookie == onecookie=data; (0/3) - Set-Cookie == secondcookie=2data; (1/3) - Set-Cookie == cookie3=data3; (2/3) + Set-Cookie == onecookie=data; (0/4) + Set-Cookie == secondcookie=2data; (1/4) + Set-Cookie == cookie3=data3; (2/4) + set-cookie == lowercase=data4; (3/4) Location == /19450002 (0/1) From 3341da7dc27256373de366b163e6b70a6606ba38 Mon Sep 17 00:00:00 2001 From: Joshua Rogers Date: Wed, 9 Sep 2026 17:04:42 +0200 Subject: [PATCH 102/481] happy eyeballs: bound work from many duplicate/failing addresses A DNS response with many duplicate addresses that fail to connect synchronously could stall a transfer: failed attempts were never pruned from the running list (cf_ip_ballers_prune only removes still-ongoing ones), so every new address triggered a full rescan and an O(n) tail-walk to append, and the connection timeout was only checked after the loop, not during it. Free hard-failed attempts immediately instead of accumulating them, track a tail pointer for O(1) appends, and check the timeout on each iteration. Also cap and deduplicate the addresses converted from a c-ares response, closing off the unbounded input side. Closes #22897 --- lib/cf-ip-happy.c | 47 ++++++++++++++++++++++++++++++++------------ lib/vdns/asyn-ares.c | 24 +++++++++++++++++++++- 2 files changed, 57 insertions(+), 14 deletions(-) diff --git a/lib/cf-ip-happy.c b/lib/cf-ip-happy.c index 8271557f8a6e..e3c0944d3979 100644 --- a/lib/cf-ip-happy.c +++ b/lib/cf-ip-happy.c @@ -267,6 +267,7 @@ static CURLcode cf_ip_attempt_connect(struct cf_ip_attempt *a, struct cf_ip_ballers { struct cf_ip_attempt *running; + struct cf_ip_attempt **running_tail; /* &running or &(last->next) */ struct cf_ip_attempt *winner; struct cf_ai_iter addr_iter; #ifdef USE_IPV6 @@ -278,10 +279,12 @@ struct cf_ip_ballers { cf_ip_connect_create *cf_create; /* for creating cf */ struct curltime last_attempt_started; timediff_t attempt_delay_ms; + CURLcode last_dead_result; /* result of last discarded hard failure */ int last_attempt_ai_family; uint32_t max_concurrent; uint8_t transport_peer; uint8_t tunnel_transport; + BIT(had_dead); /* a hard failure was discarded */ }; static CURLcode cf_ip_attempt_restart(struct cf_ip_attempt *a, @@ -322,6 +325,7 @@ static void cf_ip_ballers_clear(struct Curl_easy *data, bs->running = a->next; cf_ip_attempt_free(a, data); } + bs->running_tail = &bs->running; cf_ip_attempt_free(bs->winner, data); bs->winner = NULL; Curl_peer_unlink(&bs->origin); @@ -340,6 +344,7 @@ static CURLcode cf_ip_ballers_init(struct cf_ip_ballers *bs, uint32_t max_concurrent) { memset(bs, 0, sizeof(*bs)); + bs->running_tail = &bs->running; bs->cf_create = get_cf_create(transport_peer, !!tunnel_peer); if(!bs->cf_create) { failf(data, "unsupported transport type %u%s", @@ -375,6 +380,8 @@ static void cf_ip_ballers_prune(struct cf_ip_ballers *bs, a = *panchor; if(!a->result && !a->connected) { *panchor = a->next; + if(!a->next) + bs->running_tail = panchor; a->next = NULL; cf_ip_attempt_free(a, data); --ongoing; @@ -404,11 +411,17 @@ static CURLcode cf_ip_ballers_run(struct cf_ip_ballers *bs, return CURLE_OK; evaluate: + if(Curl_timeleft_now_ms(data, Curl_pgrs_now(data)) < 0) { + failf(data, "Connection timeout after %" FMT_OFF_T " ms", + Curl_pgrs_since_ms(data, NULL, TIMER_STARTSINGLE)); + return CURLE_OPERATION_TIMEDOUT; + } + ongoing = inconclusive = 0; /* check if a running baller connects now */ VERBOSE(i = -1); - for(panchor = &bs->running; *panchor; panchor = &(*panchor)->next) { + for(panchor = &bs->running; *panchor;) { VERBOSE(++i); a = *panchor; a->result = cf_ip_attempt_connect(a, data, connected); @@ -425,13 +438,27 @@ static CURLcode cf_ip_ballers_run(struct cf_ip_ballers *bs, bs->running = a->next; cf_ip_attempt_free(a, data); } + bs->running_tail = &bs->running; return CURLE_OK; } /* still running */ ++ongoing; + panchor = &(*panchor)->next; } - else if(a->inconclusive) /* failed, but inconclusive */ + else if(a->inconclusive) { /* failed, but inconclusive */ ++inconclusive; + panchor = &(*panchor)->next; + } + else { + /* hard failure, discard right away */ + *panchor = a->next; + if(!a->next) + bs->running_tail = panchor; + a->next = NULL; + bs->last_dead_result = a->result; + bs->had_dead = TRUE; + cf_ip_attempt_free(a, data); + } } if(bs->running) CURL_TRC_CF(data, cf, "checked connect attempts: " @@ -505,10 +532,9 @@ static CURLcode cf_ip_ballers_run(struct cf_ip_ballers *bs, DEBUGASSERT(a); /* append to running list */ - panchor = &bs->running; - while(*panchor) - panchor = &(*panchor)->next; - *panchor = a; + *bs->running_tail = a; + a->next = NULL; + bs->running_tail = &a->next; bs->last_attempt_started = *pnow; bs->last_attempt_ai_family = ai_family; /* and run everything again */ @@ -548,13 +574,8 @@ static CURLcode cf_ip_ballers_run(struct cf_ip_ballers *bs, else if(!ongoing && dns_resolved) { /* no more addresses, no inconclusive attempts */ CURL_TRC_CF(data, cf, "no more attempts to try"); - result = CURLE_COULDNT_CONNECT; - VERBOSE(i = 0); - for(a = bs->running; a; a = a->next) { - CURL_TRC_CF(data, cf, "baller %d: result=%d", i, (int)a->result); - if(a->result) - result = a->result; - } + DEBUGASSERT(!bs->running); + result = bs->had_dead ? bs->last_dead_result : CURLE_COULDNT_CONNECT; } } diff --git a/lib/vdns/asyn-ares.c b/lib/vdns/asyn-ares.c index dfb087599527..e74e3523c7fb 100644 --- a/lib/vdns/asyn-ares.c +++ b/lib/vdns/asyn-ares.c @@ -401,6 +401,22 @@ CURLcode Curl_async_await(struct Curl_easy *data, uint32_t resolv_id, return result; } +#define CARES_MAX_ADDRS 256 + +static bool async_ares_addr_seen(const struct Curl_addrinfo *cafirst, + int family, size_t ss_size, + const void *addr) +{ + const struct Curl_addrinfo *ca; + for(ca = cafirst; ca; ca = ca->ai_next) { + if((ca->ai_family == family) && + ((size_t)ca->ai_addrlen == ss_size) && + !memcmp(ca->ai_addr, addr, ss_size)) + return TRUE; + } + return FALSE; +} + /* * async_ares_node2addr() converts an address list provided by c-ares * to an internal libcurl compatible list. @@ -413,8 +429,9 @@ static struct Curl_addrinfo *async_ares_node2addr( struct Curl_addrinfo *cafirst = NULL; struct Curl_addrinfo *calast = NULL; int error = 0; + unsigned int naddrs = 0; - for(ai = node; ai; ai = ai->ai_next) { + for(ai = node; ai && (naddrs < CARES_MAX_ADDRS); ai = ai->ai_next) { size_t ss_size; struct Curl_addrinfo *ca; /* ignore elements with unsupported address family, @@ -436,6 +453,10 @@ static struct Curl_addrinfo *async_ares_node2addr( if((size_t)ai->ai_addrlen < ss_size) continue; + /* ignore duplicate addresses already collected */ + if(async_ares_addr_seen(cafirst, ai->ai_family, ss_size, ai->ai_addr)) + continue; + ca = curlx_malloc(sizeof(struct Curl_addrinfo) + ss_size); if(!ca) { error = EAI_MEMORY; @@ -465,6 +486,7 @@ static struct Curl_addrinfo *async_ares_node2addr( if(calast) calast->ai_next = ca; calast = ca; + ++naddrs; } /* if we failed, destroy the Curl_addrinfo list */ From 9e6673fb7d55b40d18383f49f465f1bd1cfce1ee Mon Sep 17 00:00:00 2001 From: ldm0 Date: Fri, 11 Sep 2026 00:09:34 +0800 Subject: [PATCH 103/481] conncache: avoid evicting connect-only connections Apply global idle checks to the per-host limit so CONNECT_ONLY sockets remain available to the application. Closes #22902 --- lib/conncache.c | 3 +- tests/data/Makefile.am | 2 + tests/data/test3231 | 46 +++++++++++++ tests/data/test3232 | 46 +++++++++++++ tests/libtest/Makefile.inc | 1 + tests/libtest/lib3231.c | 131 +++++++++++++++++++++++++++++++++++++ 6 files changed, 228 insertions(+), 1 deletion(-) create mode 100644 tests/data/test3231 create mode 100644 tests/data/test3232 create mode 100644 tests/libtest/lib3231.c diff --git a/lib/conncache.c b/lib/conncache.c index 6c90b60491cf..845dedf431a4 100644 --- a/lib/conncache.c +++ b/lib/conncache.c @@ -323,7 +323,8 @@ static struct connectdata *cpool_bundle_get_oldest_idle( while(curr) { conn = Curl_node_elem(curr); - if(!CONN_INUSE(conn)) { + /* CONNECT_ONLY sockets remain in use by the application. */ + if(!CONN_INUSE(conn) && !conn->bits.close && !conn->bits.connect_only) { /* Set higher score for the age passed since the connection was used */ unused_ms = curlx_ptimediff_ms(pnow, &conn->created) - conn->lastused_ms; if(unused_ms > oldest_ms) { diff --git a/tests/data/Makefile.am b/tests/data/Makefile.am index 1e0f14c67f8d..0c99ae63b570 100644 --- a/tests/data/Makefile.am +++ b/tests/data/Makefile.am @@ -2090,6 +2090,8 @@ TESTCASES = \ test3228 \ test3229 \ test3230 \ + test3231 \ + test3232 \ \ test3300 \ test3301 \ diff --git a/tests/data/test3231 b/tests/data/test3231 new file mode 100644 index 000000000000..37b2cf3e1ad0 --- /dev/null +++ b/tests/data/test3231 @@ -0,0 +1,46 @@ + + + + +HTTP +CONNECT_ONLY +CURLMOPT_MAX_HOST_CONNECTIONS + + + + + +HTTP/1.1 200 OK +Content-Length: 0 +Connection: close + + + + + + +http + + +http + + +CONNECT_ONLY survives the per-host connection limit + + +lib%TESTNUMBER + + +http://%HOSTIP:%HTTPPORT/%TESTNUMBER + + + + + +GET /%TESTNUMBER HTTP/1.1 +Host: %HOSTIP:%HTTPPORT +Accept: */* + + + + diff --git a/tests/data/test3232 b/tests/data/test3232 new file mode 100644 index 000000000000..488118db6f2c --- /dev/null +++ b/tests/data/test3232 @@ -0,0 +1,46 @@ + + + + +HTTP +CONNECT_ONLY +CURLMOPT_MAX_TOTAL_CONNECTIONS + + + + + +HTTP/1.1 200 OK +Content-Length: 0 +Connection: close + + + + + + +http + + +http + + +CONNECT_ONLY survives the total connection limit + + +lib3231 + + +http://%HOSTIP:%HTTPPORT/%TESTNUMBER + + + + + +GET /%TESTNUMBER HTTP/1.1 +Host: %HOSTIP:%HTTPPORT +Accept: */* + + + + diff --git a/tests/libtest/Makefile.inc b/tests/libtest/Makefile.inc index f02260a07eb5..b2829a8172b2 100644 --- a/tests/libtest/Makefile.inc +++ b/tests/libtest/Makefile.inc @@ -329,5 +329,6 @@ TESTS_C = \ lib3105.c \ lib3207.c \ lib3208.c \ + lib3231.c \ lib5000.c \ lib5004.c diff --git a/tests/libtest/lib3231.c b/tests/libtest/lib3231.c new file mode 100644 index 000000000000..6fb4fc94b7c5 --- /dev/null +++ b/tests/libtest/lib3231.c @@ -0,0 +1,131 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ +#include "first.h" + +static CURLcode connect_only_complete(CURLM *multi, CURL *easy) +{ + CURLcode result = CURLE_OK; + CURLMsg *msg; + int running; + int msgs_left; + int numfds; + + do { + multi_perform(multi, &running); + abort_on_test_timeout(); + if(running) + multi_poll(multi, NULL, 0, 1000, &numfds); + } while(running); + + msg = curl_multi_info_read(multi, &msgs_left); + if(!msg || msg->msg != CURLMSG_DONE || msg->easy_handle != easy || + msg->data.result != CURLE_OK || msgs_left) { + curl_mfprintf(stderr, "Unexpected transfer completion\n"); + result = TEST_ERR_FAILURE; + } + +test_cleanup: + return result; +} + +static CURLcode test_lib3231(const char *URL) +{ + CURL *connect_only = NULL; + CURL *http = NULL; + CURLM *multi = NULL; + CURLcode result = CURLE_OK; + curl_socket_t sock = CURL_SOCKET_BAD; + curl_off_t pending; + int running; + + start_test_timing(); + global_init(CURL_GLOBAL_ALL); + multi_init(multi); + if(testnum == 3232) + multi_setopt(multi, CURLMOPT_MAX_TOTAL_CONNECTIONS, 1L); + else + multi_setopt(multi, CURLMOPT_MAX_HOST_CONNECTIONS, 1L); + + easy_init(connect_only); + easy_setopt(connect_only, CURLOPT_URL, URL); + easy_setopt(connect_only, CURLOPT_CONNECT_ONLY, 1L); + easy_setopt(connect_only, CURLOPT_VERBOSE, 1L); + multi_add_handle(multi, connect_only); + + result = connect_only_complete(multi, connect_only); + if(result) + goto test_cleanup; + + result = curl_easy_getinfo(connect_only, CURLINFO_ACTIVESOCKET, &sock); + if(result) + goto test_cleanup; + if(sock == CURL_SOCKET_BAD) { + curl_mfprintf(stderr, "CONNECT_ONLY did not retain its socket\n"); + result = TEST_ERR_FAILURE; + goto test_cleanup; + } + + /* Do not send or receive on connect_only before competing for its slot: + the completed transfer has detached, but the application still owns it. */ + easy_init(http); + easy_setopt(http, CURLOPT_URL, URL); + easy_setopt(http, CURLOPT_VERBOSE, 1L); + multi_add_handle(multi, http); + multi_perform(multi, &running); + + result = curl_easy_getinfo(connect_only, CURLINFO_ACTIVESOCKET, &sock); + if(result) + goto test_cleanup; + if(sock == CURL_SOCKET_BAD) { + curl_mfprintf(stderr, + "Connection limit evicted the CONNECT_ONLY socket\n"); + result = TEST_ERR_FAILURE; + goto test_cleanup; + } + + if(curl_multi_get_offt(multi, CURLMINFO_XFERS_PENDING, &pending) || + pending != 1) { + curl_mfprintf(stderr, + "HTTP transfer did not wait for a connection slot\n"); + result = TEST_ERR_FAILURE; + goto test_cleanup; + } + + /* Releasing the application-owned connection must let HTTP proceed. */ + multi_remove_handle(multi, connect_only); + curl_easy_cleanup(connect_only); + connect_only = NULL; + result = connect_only_complete(multi, http); + +test_cleanup: + if(multi && connect_only) + curl_multi_remove_handle(multi, connect_only); + if(multi && http) + curl_multi_remove_handle(multi, http); + curl_easy_cleanup(connect_only); + curl_easy_cleanup(http); + curl_multi_cleanup(multi); + curl_global_cleanup(); + return result; +} From a7b42cc90cbcb09f4813253ac97ea96c10fd9e94 Mon Sep 17 00:00:00 2001 From: Stefan Eissing Date: Fri, 11 Sep 2026 09:23:14 +0200 Subject: [PATCH 104/481] urldata: remove ssl_primary_config from easy handle Rename the two structs for clarity: - `ssl_config_data` -> `ssl_easy_config` - `ssl_primary_config` -> `ssl_filter_config` The easy handle owned 2 `struct ssl_primary_config` when curl is built with proxy support. These structs were only intended for use during connection matching and, for a new connection, cloned into the `connectdata` struct. Their presence in the easy handle led to some misuses as they only contained "shallow" references there to config data that may change unnoticed. Remove these from the easy handle and keep them in the url connection matcher on the stack. Removal also found several places in TLS backends where the struct from the easy handle was used wrongly instead of the connection's version. This should shrink the easy handle by another ~300 bytes. Closes #22901 --- lib/ldap.c | 4 +- lib/setopt.c | 26 ++--- lib/url.c | 150 ++++++++++++++++------------ lib/urldata.h | 8 +- lib/vdns/doh.c | 2 +- lib/vquic/cf-ngtcp2-cmn.c | 2 +- lib/vquic/cf-ngtcp2-cmn.h | 2 +- lib/vquic/cf-ngtcp2-proxy.c | 2 +- lib/vquic/cf-quiche.c | 2 +- lib/vquic/vquic-tls.c | 6 +- lib/vquic/vquic-tls.h | 2 +- lib/vssh/libssh.c | 7 +- lib/vssh/libssh2.c | 10 +- lib/vssh/ssh.h | 2 +- lib/vssh/vssh.c | 11 ++- lib/vtls/apple.c | 7 +- lib/vtls/gtls.c | 58 ++++++----- lib/vtls/gtls.h | 8 +- lib/vtls/mbedtls.c | 48 +++++---- lib/vtls/openssl.c | 74 +++++++------- lib/vtls/rustls.c | 27 +++-- lib/vtls/schannel.c | 23 +++-- lib/vtls/schannel_verify.c | 9 +- lib/vtls/vtls.c | 26 ++--- lib/vtls/vtls.h | 27 +++-- lib/vtls/vtls_config.c | 192 +++++++++++++++++++++--------------- lib/vtls/vtls_config.h | 54 +++++----- lib/vtls/vtls_scache.c | 35 +++---- lib/vtls/vtls_scache.h | 2 +- lib/vtls/wolfssl.c | 58 ++++++----- tests/unit/unit3303.c | 63 ++++++------ tests/unit/unit3304.c | 2 +- 32 files changed, 500 insertions(+), 449 deletions(-) diff --git a/lib/ldap.c b/lib/ldap.c index 2676f732bb1a..8355560ecfc4 100644 --- a/lib/ldap.c +++ b/lib/ldap.c @@ -414,8 +414,8 @@ static CURLcode ldap_do(struct Curl_easy *data, bool *done) #ifdef LDAP_OPT_X_TLS if(conn->ssl_config.verifypeer) { /* OpenLDAP SDK supports BASE64 files. */ - if(data->set.ssl.primary.cert_type && - !curl_strequal(data->set.ssl.primary.cert_type, "PEM")) { + if(conn->ssl_config.cert_type && + !curl_strequal(conn->ssl_config.cert_type, "PEM")) { failf(data, "LDAP local: ERROR OpenLDAP only supports PEM cert-type"); result = CURLE_SSL_CERTPROBLEM; goto quit; diff --git a/lib/setopt.c b/lib/setopt.c index 0ac22a603d27..1dca23731c65 100644 --- a/lib/setopt.c +++ b/lib/setopt.c @@ -313,10 +313,10 @@ CURLcode Curl_setopt_SSLVERSION(struct Curl_easy *data, CURLoption option, */ { long version, version_max; - struct ssl_primary_config *primary = &data->set.ssl.primary; + struct ssl_easy_config *sslc = &data->set.ssl; #ifndef CURL_DISABLE_PROXY if(option != CURLOPT_SSLVERSION) - primary = &data->set.proxy_ssl.primary; + sslc = &data->set.proxy_ssl; #else (void)option; /* unused */ #endif @@ -333,8 +333,8 @@ CURLcode Curl_setopt_SSLVERSION(struct Curl_easy *data, CURLoption option, if(version == CURL_SSLVERSION_DEFAULT) version = CURL_SSLVERSION_TLSv1_2; - primary->version = (unsigned char)version; - primary->version_max = (unsigned int)version_max; + sslc->version = (unsigned char)version; + sslc->version_max = (unsigned int)version_max; } return CURLE_OK; } @@ -522,7 +522,7 @@ static CURLcode setopt_long_bool(struct Curl_easy *data, CURLoption option, /* * Enable peer SSL verifying for proxy. */ - s->proxy_ssl.primary.verifypeer = enabled; + s->proxy_ssl.verifypeer = enabled; /* Update the current connection proxy_ssl_config. */ Curl_ssl_conn_config_update(data, TRUE); @@ -531,7 +531,7 @@ static CURLcode setopt_long_bool(struct Curl_easy *data, CURLoption option, /* * Enable verification of the hostname in the peer certificate for proxy */ - s->proxy_ssl.primary.verifyhost = enabled; + s->proxy_ssl.verifyhost = enabled; ok = 2; /* Update the current connection proxy_ssl_config. */ Curl_ssl_conn_config_update(data, TRUE); @@ -616,7 +616,7 @@ static CURLcode setopt_long_bool(struct Curl_easy *data, CURLoption option, /* * Enable peer SSL verifying. */ - s->ssl.primary.verifypeer = enabled; + s->ssl.verifypeer = enabled; /* Update the current connection ssl_config. */ Curl_ssl_conn_config_update(data, FALSE); @@ -654,7 +654,7 @@ static CURLcode setopt_long_bool(struct Curl_easy *data, CURLoption option, /* Obviously people are not reading documentation and too many thought this argument took a boolean when it was not and misused it. Treat 1 and 2 the same */ - s->ssl.primary.verifyhost = enabled; + s->ssl.verifyhost = enabled; ok = 2; /* Update the current connection ssl_config. */ @@ -667,7 +667,7 @@ static CURLcode setopt_long_bool(struct Curl_easy *data, CURLoption option, if(!Curl_ssl_cert_status_request()) return CURLE_NOT_BUILT_IN; - s->ssl.primary.verifystatus = enabled; + s->ssl.verifystatus = enabled; /* Update the current connection ssl_config. */ Curl_ssl_conn_config_update(data, FALSE); @@ -698,9 +698,9 @@ static CURLcode setopt_long_bool(struct Curl_easy *data, CURLoption option, s->ignorecl = enabled; break; case CURLOPT_SSL_SESSIONID_CACHE: - s->ssl.primary.cache_session = enabled; + s->ssl.cache_session = enabled; #ifndef CURL_DISABLE_PROXY - s->proxy_ssl.primary.cache_session = s->ssl.primary.cache_session; + s->proxy_ssl.cache_session = s->ssl.cache_session; #endif break; #ifdef USE_SSH @@ -934,11 +934,11 @@ static CURLcode setopt_long_ssl(struct Curl_easy *data, CURLoption option, s->use_ssl = (unsigned char)arg; break; case CURLOPT_SSL_OPTIONS: - s->ssl.primary.ssl_options = (unsigned char)(arg & 0xff); + s->ssl.ssl_options = (unsigned char)(arg & 0xff); break; #ifndef CURL_DISABLE_PROXY case CURLOPT_PROXY_SSL_OPTIONS: - s->proxy_ssl.primary.ssl_options = (unsigned char)(arg & 0xff); + s->proxy_ssl.ssl_options = (unsigned char)(arg & 0xff); break; #endif case CURLOPT_SSL_ENABLE_NPN: diff --git a/lib/url.c b/lib/url.c index 5bb1c5207182..ab8c79fcdd53 100644 --- a/lib/url.c +++ b/lib/url.c @@ -294,10 +294,6 @@ CURLcode Curl_close(struct Curl_easy **datap) Curl_netrc_cleanup(&data->state.netrc); #ifndef CURL_DISABLE_DIGEST_AUTH curlx_free(data->state.envproxy); -#endif - Curl_ssl_config_cleanup(&data->set.ssl.primary); -#ifndef CURL_DISABLE_PROXY - Curl_ssl_config_cleanup(&data->set.proxy_ssl.primary); #endif curlx_memzero(data, sizeof(*data)); curlx_free(data); @@ -356,9 +352,9 @@ void Curl_init_userdefined(struct Curl_easy *data) set->httpauth = CURLAUTH_BASIC; /* defaults to basic */ - Curl_ssl_config_init(&data->set.ssl.primary); + Curl_ssl_config_init(&data->set.ssl); #ifndef CURL_DISABLE_PROXY - Curl_ssl_config_init(&data->set.proxy_ssl.primary); + Curl_ssl_config_init(&data->set.proxy_ssl); set->proxyport = 0; set->proxytype = CURLPROXY_HTTP; /* defaults to HTTP proxy */ set->proxyauth = CURLAUTH_BASIC; /* defaults to basic */ @@ -582,6 +578,10 @@ struct url_conn_match { struct Curl_easy *data; struct connectdata *needle; struct curltime now; + struct ssl_filter_config ssl_config; +#ifndef CURL_DISABLE_PROXY + struct ssl_filter_config proxy_ssl_config; +#endif BIT(may_multiplex); BIT(want_ntlm_http); BIT(want_proxy_ntlm_http); @@ -722,7 +722,7 @@ static bool url_match_ssl_use(struct connectdata *conn, return FALSE; /* We may reuse this as an auto-TLS upgrade, but only if the SSL * config parameters match. */ - if(!Curl_ssl_conn_config_match(m->data, conn, FALSE)) + if(!Curl_ssl_conn_config_match(m->data, &m->ssl_config, conn, FALSE)) return FALSE; } else if(m->require_tls) @@ -747,7 +747,8 @@ static bool url_match_proxy_use(struct connectdata *conn, if(CURL_PROXY_IS_HTTPS(m->needle->http_proxy.proxytype)) { /* https proxies come in different types, http/1.1, h2, ... */ /* match SSL config to proxy */ - if(!Curl_ssl_conn_config_match(m->data, conn, TRUE)) { + if(!Curl_ssl_conn_config_match(m->data, &m->proxy_ssl_config, + conn, TRUE)) { DEBUGF(infof(m->data, "Connection #%" FMT_OFF_T " has different SSL proxy parameters, cannot reuse", @@ -886,7 +887,7 @@ static bool url_match_ssl_config(struct connectdata *conn, { /* If talking/upgrading to TLS, conn needs to use the same SSL options. */ if(((m->needle->scheme->flags & PROTOPT_SSL) || m->may_tls) && - !Curl_ssl_conn_config_match(m->data, conn, FALSE)) { + !Curl_ssl_conn_config_match(m->data, &m->ssl_config, conn, FALSE)) { DEBUGF(infof(m->data, "Connection #%" FMT_OFF_T " has different SSL parameters, cannot reuse", conn->connection_id)); @@ -1090,58 +1091,20 @@ static bool url_match_result(void *userdata) */ static bool url_attach_existing(struct Curl_easy *data, struct connectdata *needle, - bool *waitpipe) + struct url_conn_match *m) { struct cpool *cpool = Curl_cpool_get_instance(data); - struct url_conn_match match; bool success; DEBUGASSERT(!data->conn); - memset(&match, 0, sizeof(match)); - match.data = data; - match.needle = needle; - match.now = *Curl_pgrs_now(data); - match.may_multiplex = xfer_may_multiplex(data, needle); - Curl_cpool_prune_dead(cpool, data); -#ifdef USE_NTLM - match.want_ntlm_http = - (data->state.authhost.want & CURLAUTH_NTLM) && - (needle->scheme->protocol & PROTO_FAMILY_HTTP) && - Curl_auth_allowed_to_host(data); -#ifndef CURL_DISABLE_PROXY - match.want_proxy_ntlm_http = - needle->http_proxy.creds && - (data->state.authproxy.want & CURLAUTH_NTLM) && - (needle->scheme->protocol & PROTO_FAMILY_HTTP); -#endif -#endif - -#if !defined(CURL_DISABLE_HTTP) && defined(USE_SPNEGO) - match.want_nego_http = - (data->state.authhost.want & CURLAUTH_NEGOTIATE) && - (needle->scheme->protocol & PROTO_FAMILY_HTTP) && - Curl_auth_allowed_to_host(data); -#ifndef CURL_DISABLE_PROXY - match.want_proxy_nego_http = - needle->http_proxy.creds && - (data->state.authproxy.want & CURLAUTH_NEGOTIATE) && - (needle->scheme->protocol & PROTO_FAMILY_HTTP); -#endif -#endif - match.require_tls = data->set.use_ssl >= CURLUSESSL_CONTROL; - match.may_tls = data->set.use_ssl > CURLUSESSL_NONE; - /* Find a connection in the pool that matches what "data + needle" * requires. If a suitable candidate is found, it is attached to "data". */ success = Curl_cpool_find(data, needle->destination, - url_match_conn, url_match_result, &match); + url_match_conn, url_match_result, m); - /* wait_pipe is TRUE if we encounter a bundle that is undecided. There - * is no matching connection then, yet. */ - *waitpipe = (bool)match.wait_pipe; return success; } @@ -2199,6 +2162,62 @@ static CURLcode url_set_data_origin_and_creds(struct Curl_easy *data) return result; } +static CURLcode url_match_init(struct Curl_easy *data, + struct connectdata *needle, + struct url_conn_match *m) +{ + memset(m, 0, sizeof(*m)); + m->data = data; + m->needle = needle; + m->now = *Curl_pgrs_now(data); + m->may_multiplex = xfer_may_multiplex(data, needle); + +#ifdef USE_NTLM + m->want_ntlm_http = + (data->state.authhost.want & CURLAUTH_NTLM) && + (needle->scheme->protocol & PROTO_FAMILY_HTTP) && + Curl_auth_allowed_to_host(data); +#ifndef CURL_DISABLE_PROXY + m->want_proxy_ntlm_http = + needle->http_proxy.creds && + (data->state.authproxy.want & CURLAUTH_NTLM) && + (needle->scheme->protocol & PROTO_FAMILY_HTTP); +#endif +#endif + +#if !defined(CURL_DISABLE_HTTP) && defined(USE_SPNEGO) + m->want_nego_http = + (data->state.authhost.want & CURLAUTH_NEGOTIATE) && + (needle->scheme->protocol & PROTO_FAMILY_HTTP) && + Curl_auth_allowed_to_host(data); +#ifndef CURL_DISABLE_PROXY + m->want_proxy_nego_http = + needle->http_proxy.creds && + (data->state.authproxy.want & CURLAUTH_NEGOTIATE) && + (needle->scheme->protocol & PROTO_FAMILY_HTTP); +#endif +#endif + m->require_tls = data->set.use_ssl >= CURLUSESSL_CONTROL; + m->may_tls = data->set.use_ssl > CURLUSESSL_NONE; + + /* Get a shallow setup of filter configs for connection cache matching */ + return Curl_ssl_filter_config_tmp_init(data, needle->origin, + &m->ssl_config, +#ifndef CURL_DISABLE_PROXY + &m->proxy_ssl_config); +#else + NULL); +#endif +} + +static void url_match_destroy(struct url_conn_match *m) +{ + Curl_ssl_config_cleanup(&m->ssl_config); +#ifndef CURL_DISABLE_PROXY + Curl_ssl_config_cleanup(&m->proxy_ssl_config); +#endif +} + /** * Find an existing connection for the transfer or create a new one. * Returns @@ -2211,7 +2230,8 @@ static CURLcode url_find_or_create_conn(struct Curl_easy *data, const struct curltime *pnow) { struct connectdata *needle = NULL; - bool waitpipe = FALSE; + struct url_conn_match match; + bool match_initialized = FALSE; CURLcode result; /* create the template connection for transfer data. Use this needle to @@ -2222,6 +2242,11 @@ static CURLcode url_find_or_create_conn(struct Curl_easy *data, goto out; DEBUGASSERT(needle); + result = url_match_init(data, needle, &match); + if(result) + goto out; + match_initialized = TRUE; + /*********************************************************************** * file: is a special case in that it does not need a network connection ***********************************************************************/ @@ -2260,11 +2285,6 @@ static CURLcode url_find_or_create_conn(struct Curl_easy *data, } #endif - /* Complete the easy's SSL configuration for connection cache matching */ - result = Curl_ssl_easy_config_complete(data, needle->origin); - if(result) - goto out; - /************************************************************* * Reuse of existing connection is not allowed when * - connect_only is set or @@ -2274,7 +2294,7 @@ static CURLcode url_find_or_create_conn(struct Curl_easy *data, if((!data->set.reuse_fresh || data->state.followlocation) && !data->set.connect_only) { /* Ok, try to find and attach an existing one */ - url_attach_existing(data, needle, &waitpipe); + url_attach_existing(data, needle, &match); } if(data->conn) { @@ -2308,7 +2328,7 @@ static CURLcode url_find_or_create_conn(struct Curl_easy *data, that if we have reached the limit of how many connections we are allowed to open. */ - if(waitpipe) { + if(match.wait_pipe) { /* There is a connection that *might* become usable for multiplexing "soon", and we wait for that */ infof(data, "Waiting on connection to negotiate possible multiplexing."); @@ -2337,11 +2357,17 @@ static CURLcode url_find_or_create_conn(struct Curl_easy *data, } } - /* Convert needle into a full connection by filling in all the - * remaining parts like the cloned SSL configuration. */ - result = Curl_ssl_conn_config_init(data, needle); + /* Convert needle into a full connection by cloning the + * ssl filter config used in matching into the connection. */ + result = Curl_ssl_conn_config_clone(&match.ssl_config, +#ifndef CURL_DISABLE_PROXY + &match.proxy_ssl_config, +#else + NULL, +#endif + needle); if(result) { - DEBUGF(curl_mfprintf(stderr, "Error: init connection SSL config\n")); + DEBUGF(infof(data, "Error: clone connection SSL config")); goto out; } @@ -2399,6 +2425,8 @@ static CURLcode url_find_or_create_conn(struct Curl_easy *data, result = Curl_conn_ev_data_setup(data); out: + if(match_initialized) + url_match_destroy(&match); if(needle) Curl_conn_free(data, needle); DEBUGASSERT(result || data->conn); diff --git a/lib/urldata.h b/lib/urldata.h index 5e36c631e58f..1633e337c4a3 100644 --- a/lib/urldata.h +++ b/lib/urldata.h @@ -318,9 +318,9 @@ struct connectdata { curl_closesocket_callback fclosesocket; /* function closing the socket(s) */ void *closesocket_client; - struct ssl_primary_config ssl_config; + struct ssl_filter_config ssl_config; #ifndef CURL_DISABLE_PROXY - struct ssl_primary_config proxy_ssl_config; + struct ssl_filter_config proxy_ssl_config; #endif char *options; /* options string, allocated */ @@ -893,11 +893,11 @@ struct UserDefined { struct curl_slist *connect_to; /* list of host:port mappings to override the hostname and port to connect to */ time_t timevalue; /* what time to compare with */ - struct ssl_config_data ssl; /* user defined SSL stuff */ + struct ssl_easy_config ssl; /* user defined SSL stuff */ curl_ssl_ctx_callback ssl_fsslctx; /* function to initialize SSL ctx */ void *ssl_fsslctxp; /* parameter for callback */ #ifndef CURL_DISABLE_PROXY - struct ssl_config_data proxy_ssl; /* user defined SSL stuff for proxy */ + struct ssl_easy_config proxy_ssl; /* user defined SSL stuff for proxy */ struct curl_slist *proxyheaders; /* linked list of extra CONNECT headers */ uint16_t proxyport; /* If non-zero, use this port number by default. If the proxy string features a diff --git a/lib/vdns/doh.c b/lib/vdns/doh.c index 04bc8d0ec20e..caf058f5161f 100644 --- a/lib/vdns/doh.c +++ b/lib/vdns/doh.c @@ -363,7 +363,7 @@ static CURLcode doh_probe_run(struct Curl_easy *data, } (void)curl_easy_setopt(doh, CURLOPT_SSL_OPTIONS, - ((long)data->set.ssl.primary.ssl_options & + ((long)data->set.ssl.ssl_options & ~CURLSSLOPT_AUTO_CLIENT_CERT)); } diff --git a/lib/vquic/cf-ngtcp2-cmn.c b/lib/vquic/cf-ngtcp2-cmn.c index f80e56e341f7..2b6cec68b0f7 100644 --- a/lib/vquic/cf-ngtcp2-cmn.c +++ b/lib/vquic/cf-ngtcp2-cmn.c @@ -118,7 +118,7 @@ void Curl_cf_ngtcp2_h3_err_set(struct Curl_cfilter *cf, CURLcode Curl_cf_ngtcp2_ctx_init(struct cf_ngtcp2_ctx *ctx, struct Curl_peer *origin, struct Curl_peer *peer, - struct ssl_primary_config *sslc, + struct ssl_filter_config *sslc, cf_ngtcp2_init_h3_conn *init_h3_conn_cb) { DEBUGASSERT(!ctx->initialized); diff --git a/lib/vquic/cf-ngtcp2-cmn.h b/lib/vquic/cf-ngtcp2-cmn.h index fa71cfddc7c9..530603860f60 100644 --- a/lib/vquic/cf-ngtcp2-cmn.h +++ b/lib/vquic/cf-ngtcp2-cmn.h @@ -154,7 +154,7 @@ struct cf_ngtcp2_ctx { CURLcode Curl_cf_ngtcp2_ctx_init(struct cf_ngtcp2_ctx *ctx, struct Curl_peer *origin, struct Curl_peer *peer, - struct ssl_primary_config *sslc, + struct ssl_filter_config *sslc, cf_ngtcp2_init_h3_conn *init_h3_conn_cb); void Curl_cf_ngtcp2_ctx_cleanup(struct cf_ngtcp2_ctx *ctx); void Curl_cf_ngtcp2_cmn_err_set(struct Curl_cfilter *cf, diff --git a/lib/vquic/cf-ngtcp2-proxy.c b/lib/vquic/cf-ngtcp2-proxy.c index 3b2cf8425e5a..b68f5c888aca 100644 --- a/lib/vquic/cf-ngtcp2-proxy.c +++ b/lib/vquic/cf-ngtcp2-proxy.c @@ -175,7 +175,7 @@ static CURLcode cf_ngtcp2_proxy_h3_init(struct Curl_cfilter *cf, static CURLcode cf_h3_proxy_ctx_init(struct cf_h3_proxy_ctx *ctx, struct Curl_peer *origin, struct Curl_peer *peer, - struct ssl_primary_config *sslc, + struct ssl_filter_config *sslc, struct Curl_peer *tunnel_peer, uint8_t tunnel_transport) { diff --git a/lib/vquic/cf-quiche.c b/lib/vquic/cf-quiche.c index 934a56cadaf1..adeea37733e8 100644 --- a/lib/vquic/cf-quiche.c +++ b/lib/vquic/cf-quiche.c @@ -112,7 +112,7 @@ static void h3_stream_hash_free(unsigned int id, void *stream); static CURLcode cf_quiche_ctx_init(struct cf_quiche_ctx *ctx, struct Curl_peer *origin, struct Curl_peer *peer, - struct ssl_primary_config *sslc) + struct ssl_filter_config *sslc) { DEBUGASSERT(!ctx->initialized); #ifdef DEBUG_QUICHE diff --git a/lib/vquic/vquic-tls.c b/lib/vquic/vquic-tls.c index 75e2f4e1e2c4..94f69efa9081 100644 --- a/lib/vquic/vquic-tls.c +++ b/lib/vquic/vquic-tls.c @@ -51,7 +51,7 @@ CURLcode Curl_vquic_tls_peer_init(struct Curl_peer *origin, struct Curl_peer *peer, - struct ssl_primary_config *sslc, + struct ssl_filter_config *sslc, struct ssl_peer *ssl_peer) { char tls_id[80]; @@ -155,10 +155,10 @@ CURLcode Curl_vquic_tls_verify_peer(struct curl_tls_ctx *ctx, struct Curl_easy *data, struct ssl_peer *peer) { - struct ssl_primary_config *conn_config; + struct ssl_filter_config *conn_config; CURLcode result = CURLE_OK; - conn_config = Curl_ssl_cf_get_primary_config(cf); + conn_config = Curl_ssl_cf_get_filter_config(cf); if(!conn_config) return CURLE_FAILED_INIT; diff --git a/lib/vquic/vquic-tls.h b/lib/vquic/vquic-tls.h index e8d2418b0611..0e7f34dff760 100644 --- a/lib/vquic/vquic-tls.h +++ b/lib/vquic/vquic-tls.h @@ -68,7 +68,7 @@ typedef CURLcode Curl_vquic_session_reuse_cb(struct Curl_cfilter *cf, CURLcode Curl_vquic_tls_peer_init(struct Curl_peer *origin, struct Curl_peer *peer, - struct ssl_primary_config *sslc, + struct ssl_filter_config *sslc, struct ssl_peer *ssl_peer); /** diff --git a/lib/vssh/libssh.c b/lib/vssh/libssh.c index 5cc64b0396c7..ca607be3aa21 100644 --- a/lib/vssh/libssh.c +++ b/lib/vssh/libssh.c @@ -865,7 +865,7 @@ static int myssh_in_AUTH_PKEY_INIT(struct Curl_easy *data, /* Two choices, (1) private key was given on CMD, * (2) use the "default" keys. */ if(sshc->priv_key) { - if(sshc->pubkey && !data->set.ssl.primary.key_passwd) { + if(sshc->pubkey && !sshc->passphrase) { rc = ssh_userauth_try_publickey(sshc->ssh_session, NULL, sshc->pubkey); if(rc == SSH_AUTH_AGAIN) return SSH_AGAIN; @@ -877,7 +877,7 @@ static int myssh_in_AUTH_PKEY_INIT(struct Curl_easy *data, } rc = ssh_pki_import_privkey_file(sshc->priv_key, - data->set.ssl.primary.key_passwd, NULL, + sshc->passphrase, NULL, NULL, &sshc->privkey); if(rc != SSH_OK) { failf(data, "Could not load private key file %s", sshc->priv_key); @@ -889,7 +889,7 @@ static int myssh_in_AUTH_PKEY_INIT(struct Curl_easy *data, } else { rc = ssh_userauth_publickey_auto(sshc->ssh_session, NULL, - data->set.ssl.primary.key_passwd); + sshc->passphrase); if(rc == SSH_AUTH_AGAIN) return SSH_AGAIN; @@ -1916,6 +1916,7 @@ static void sshc_cleanup(struct ssh_conn *sshc) sshc->pubkey = NULL; } + curlx_safefree(sshc->passphrase); curlx_safefree(sshc->pub_key); curlx_safefree(sshc->priv_key); curlx_safefree(sshc->quote_path1); diff --git a/lib/vssh/libssh2.c b/lib/vssh/libssh2.c index 6afa1c223b4e..2096fe34946f 100644 --- a/lib/vssh/libssh2.c +++ b/lib/vssh/libssh2.c @@ -1509,12 +1509,9 @@ static CURLcode ssh_state_auth_pkey(struct Curl_easy *data, */ struct connectdata *conn = data->conn; const char *user = Curl_creds_user(conn->creds); - int rc = - libssh2_userauth_publickey_fromfile_ex(sshc->ssh_session, - user, - curlx_uztoui(strlen(user)), - sshc->pub_key, - sshc->priv_key, sshc->passphrase); + int rc = libssh2_userauth_publickey_fromfile_ex( + sshc->ssh_session, user, curlx_uztoui(strlen(user)), + sshc->pub_key, sshc->priv_key, sshc->passphrase ? sshc->passphrase : ""); if(rc == LIBSSH2_ERROR_EAGAIN) return CURLE_AGAIN; @@ -2577,6 +2574,7 @@ static CURLcode sshc_cleanup(struct ssh_conn *sshc, struct Curl_easy *data, DEBUGASSERT(!sshc->kh); DEBUGASSERT(!sshc->ssh_agent); + curlx_safefree(sshc->passphrase); curlx_safefree(sshc->pub_key); curlx_safefree(sshc->priv_key); curlx_safefree(sshc->quote_path1); diff --git a/lib/vssh/ssh.h b/lib/vssh/ssh.h index 52bc1399901a..b1071419fdf9 100644 --- a/lib/vssh/ssh.h +++ b/lib/vssh/ssh.h @@ -143,7 +143,7 @@ struct ssh_conn { const char *authlist; /* List of auth. methods, managed by libssh2 */ /* common */ - const char *passphrase; /* pass-phrase to use */ + char *passphrase; /* strdup'ed pass-phrase to use or NULL */ char *pub_key; /* strdup'ed public key file */ char *priv_key; /* strdup'ed private key file */ sshstate state; /* always use ssh.c:state() to change state! */ diff --git a/lib/vssh/vssh.c b/lib/vssh/vssh.c index 2a1bbb0e76b1..1aa8effa922c 100644 --- a/lib/vssh/vssh.c +++ b/lib/vssh/vssh.c @@ -427,9 +427,14 @@ CURLcode Curl_ssh_setup_pkey(struct Curl_easy *data, struct ssh_conn *sshc) goto fail; } - sshc->passphrase = data->set.ssl.primary.key_passwd; - if(!sshc->passphrase) - sshc->passphrase = ""; + { + const char *keypasswd = CURL_EASY_STR(data, STRING_KEY_PASSWD); + if(keypasswd) { + sshc->passphrase = curlx_strdup(keypasswd); + if(!sshc->passphrase) + goto fail; + } + } if(sshc->pub_key) infof(data, "SSH: public key file '%s'", sshc->pub_key); diff --git a/lib/vtls/apple.c b/lib/vtls/apple.c index ce7f0c5e1cd2..c6996f295ec8 100644 --- a/lib/vtls/apple.c +++ b/lib/vtls/apple.c @@ -87,7 +87,7 @@ CURLcode Curl_vtls_apple_verify(struct Curl_cfilter *cf, const unsigned char *ocsp_buf, size_t ocsp_len) { - struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); + struct ssl_filter_config *conn_config = Curl_ssl_cf_get_filter_config(cf); CURLcode result = CURLE_OK; SecTrustRef trust = NULL; SecPolicyRef policy = NULL; @@ -127,8 +127,7 @@ CURLcode Curl_vtls_apple_verify(struct Curl_cfilter *cf, #if defined(HAVE_BUILTIN_AVAILABLE) && defined(SUPPORTS_SecOCSP) { - struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); - if(!ssl_config->no_revoke) { + if(!conn_config->no_revoke) { if(__builtin_available(macOS 10.9, iOS 7, tvOS 9, watchOS 2, *)) { /* Even without this set, validation seemingly-unavoidably fails * for certificates that trustd already knows to be revoked. @@ -144,7 +143,7 @@ CURLcode Curl_vtls_apple_verify(struct Curl_cfilter *cf, * It seems that applications using this policy are expected to PIN * their certificate public keys or verification fails. * This does not seem to be what we want here. */ - if(!ssl_config->revoke_best_effort) { + if(!conn_config->revoke_best_effort) { revocation_flags |= kSecRevocationRequirePositiveResponse; } #endif diff --git a/lib/vtls/gtls.c b/lib/vtls/gtls.c index f298a24093ae..06bb2b08f410 100644 --- a/lib/vtls/gtls.c +++ b/lib/vtls/gtls.c @@ -323,7 +323,7 @@ static gnutls_x509_crt_fmt_t gnutls_do_file_type(const char *type) static CURLcode gnutls_set_ssl_version_min_max( struct Curl_easy *data, struct ssl_peer *peer, - struct ssl_primary_config *conn_config, + struct ssl_filter_config *conn_config, const char **prioritylist, bool tls13support) { @@ -446,8 +446,8 @@ static CURLcode gtls_populate_creds(struct Curl_cfilter *cf, struct Curl_easy *data, gnutls_certificate_credentials_t creds) { - struct ssl_primary_config *config = Curl_ssl_cf_get_primary_config(cf); - struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); + struct ssl_filter_config *config = Curl_ssl_cf_get_filter_config(cf); + struct ssl_easy_config *ssl_config = Curl_ssl_cf_get_easy_config(cf, data); bool creds_are_empty = TRUE; int rc; @@ -582,7 +582,7 @@ static bool gtls_shared_creds_expired(struct Curl_easy *data, static bool gtls_shared_creds_different(struct Curl_cfilter *cf, const struct gtls_shared_creds *sc) { - struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); + struct ssl_filter_config *conn_config = Curl_ssl_cf_get_filter_config(cf); if(!sc->CAfile || !conn_config->CAfile) return sc->CAfile != conn_config->CAfile; @@ -622,7 +622,7 @@ static void gtls_set_cached_creds(struct Curl_cfilter *cf, struct Curl_easy *data, struct gtls_shared_creds *sc) { - struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); + struct ssl_filter_config *conn_config = Curl_ssl_cf_get_filter_config(cf); DEBUGASSERT(sc); DEBUGASSERT(sc->creds); @@ -653,8 +653,7 @@ CURLcode Curl_gtls_client_trust_setup(struct Curl_cfilter *cf, struct Curl_easy *data, struct gtls_ctx *gtls) { - struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); - struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); + struct ssl_filter_config *conn_config = Curl_ssl_cf_get_filter_config(cf); struct gtls_shared_creds *cached_creds = NULL; bool cache_criteria_met; CURLcode result; @@ -667,7 +666,7 @@ CURLcode Curl_gtls_client_trust_setup(struct Curl_cfilter *cf, conn_config->verifypeer && !conn_config->CApath && !conn_config->ca_info_blob && - !ssl_config->primary.CRLfile && + !conn_config->CRLfile && !conn_config->native_ca_store && !conn_config->clientcert; /* GnuTLS adds client cert to its credentials! */ @@ -841,7 +840,7 @@ static CURLcode gtls_set_priority(struct Curl_cfilter *cf, struct gtls_ctx *gtls, const char *priority) { - struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); + struct ssl_filter_config *conn_config = Curl_ssl_cf_get_filter_config(cf); struct dynbuf buf; const char *err = NULL; CURLcode result = CURLE_OK; @@ -886,8 +885,8 @@ static CURLcode gtls_client_init(struct Curl_cfilter *cf, size_t earlydata_max, struct gtls_ctx *gtls) { - struct ssl_primary_config *config = Curl_ssl_cf_get_primary_config(cf); - struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); + struct ssl_filter_config *config = Curl_ssl_cf_get_filter_config(cf); + struct ssl_easy_config *ssl_config = Curl_ssl_cf_get_easy_config(cf, data); unsigned int init_flags; int rc; const char *prioritylist; @@ -982,11 +981,11 @@ static CURLcode gtls_client_init(struct Curl_cfilter *cf, if(result) return result; } - if(ssl_config->primary.cert_type && - curl_strequal(ssl_config->primary.cert_type, "P12")) { + if(config->cert_type && + curl_strequal(config->cert_type, "P12")) { rc = gnutls_certificate_set_x509_simple_pkcs12_file( gtls->shared_creds->creds, config->clientcert, GNUTLS_X509_FMT_DER, - ssl_config->primary.key_passwd ? ssl_config->primary.key_passwd : ""); + config->key_passwd ? config->key_passwd : ""); if(rc != GNUTLS_E_SUCCESS) { failf(data, "error reading X.509 potentially-encrypted key or certificate " @@ -1004,15 +1003,14 @@ static CURLcode gtls_client_init(struct Curl_cfilter *cf, rc = gnutls_certificate_set_x509_key_file2( gtls->shared_creds->creds, config->clientcert, - ssl_config->primary.key ? ssl_config->primary.key : - config->clientcert, - gnutls_do_file_type(ssl_config->primary.cert_type), - ssl_config->primary.key_passwd, + config->key ? config->key : config->clientcert, + gnutls_do_file_type(config->cert_type), + config->key_passwd, supported_key_encryption_algorithms); if(rc != GNUTLS_E_SUCCESS) { failf(data, "error reading X.509 %skey file: %s", - ssl_config->primary.key_passwd ? "potentially-encrypted " : "", + config->key_passwd ? "potentially-encrypted " : "", gnutls_strerror(rc)); return CURLE_SSL_CONNECT_ERROR; } @@ -1085,7 +1083,7 @@ static CURLcode gtls_apply_session( struct Curl_ssl_session *scs, bool *pwas_setup) { - struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); + struct ssl_filter_config *conn_config = Curl_ssl_cf_get_filter_config(cf); CURLcode result = CURLE_OK; int rc; @@ -1104,7 +1102,7 @@ static CURLcode gtls_apply_session( else { infof(data, "SSL reusing session with ALPN '%s'", scs->alpn ? scs->alpn : "-"); - if(ssl_config->earlydata && scs->alpn && + if(conn_config->earlydata && scs->alpn && !cf->conn->bits.connect_only) { bool do_early_data = FALSE; if(sess_reuse_cb) { @@ -1134,7 +1132,7 @@ CURLcode Curl_gtls_ctx_init(struct gtls_ctx *gctx, void *ssl_user_data, Curl_gtls_init_session_reuse_cb *sess_reuse_cb) { - struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); + struct ssl_filter_config *conn_config = Curl_ssl_cf_get_filter_config(cf); struct Curl_ssl_session *scs = NULL; gnutls_datum_t gtls_alpns[ALPN_ENTRIES_MAX]; size_t gtls_alpns_count = 0; @@ -1589,8 +1587,8 @@ static CURLcode gtls_chain_get_der(struct Curl_cfilter *cf, ssl_config->certverifyresult as one or more gnutls_certificate_status_t enumerated elements bitwise or'd. */ static CURLcode gtls_verify_cert(struct Curl_easy *data, - struct ssl_primary_config *config, - struct ssl_config_data *ssl_config, + struct ssl_filter_config *config, + struct ssl_easy_config *ssl_config, gnutls_session_t session, struct Curl_cfilter *cf, struct ssl_peer *peer, @@ -1643,8 +1641,8 @@ static CURLcode gtls_verify_cert(struct Curl_easy *data, failf(data, "SSL certificate verification failed: %s. (CAfile: %s " "CRLfile: %s)", cause, config->CAfile ? config->CAfile : "none", - ssl_config->primary.CRLfile ? - ssl_config->primary.CRLfile : "none"); + config->CRLfile ? + config->CRLfile : "none"); return CURLE_PEER_FAILED_VERIFICATION; } @@ -1654,8 +1652,8 @@ static CURLcode gtls_verify_cert(struct Curl_easy *data, CURLcode Curl_gtls_verifyserver(struct Curl_cfilter *cf, struct Curl_easy *data, gnutls_session_t session, - struct ssl_primary_config *config, - struct ssl_config_data *ssl_config, + struct ssl_filter_config *config, + struct ssl_easy_config *ssl_config, struct ssl_peer *peer, const char *pinned_key) { @@ -1871,8 +1869,8 @@ static CURLcode gtls_verifyserver(struct Curl_cfilter *cf, gnutls_session_t session) { struct ssl_connect_data *connssl = cf->ctx; - struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); - struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); + struct ssl_filter_config *conn_config = Curl_ssl_cf_get_filter_config(cf); + struct ssl_easy_config *ssl_config = Curl_ssl_cf_get_easy_config(cf, data); #ifndef CURL_DISABLE_PROXY const char *pinned_key = Curl_ssl_cf_is_proxy(cf) ? CURL_EASY_STR(data, STRING_SSL_PINNEDPUBLICKEY_PROXY) : diff --git a/lib/vtls/gtls.h b/lib/vtls/gtls.h index 0ad9a7e68c72..6ca39f13e3df 100644 --- a/lib/vtls/gtls.h +++ b/lib/vtls/gtls.h @@ -34,8 +34,8 @@ struct Curl_easy; struct Curl_cfilter; struct alpn_spec; -struct ssl_primary_config; -struct ssl_config_data; +struct ssl_filter_config; +struct ssl_easy_config; struct ssl_peer; struct ssl_connect_data; struct Curl_ssl_session; @@ -89,8 +89,8 @@ CURLcode Curl_gtls_client_trust_setup(struct Curl_cfilter *cf, CURLcode Curl_gtls_verifyserver(struct Curl_cfilter *cf, struct Curl_easy *data, gnutls_session_t session, - struct ssl_primary_config *config, - struct ssl_config_data *ssl_config, + struct ssl_filter_config *config, + struct ssl_easy_config *ssl_config, struct ssl_peer *peer, const char *pinned_key); diff --git a/lib/vtls/mbedtls.c b/lib/vtls/mbedtls.c index 9678cb1fc606..7de9f81502ab 100644 --- a/lib/vtls/mbedtls.c +++ b/lib/vtls/mbedtls.c @@ -182,7 +182,7 @@ static int mbedtls_bio_cf_read(void *bio, unsigned char *buf, size_t blen) static CURLcode mbed_set_ssl_version_min_max( struct Curl_easy *data, struct mbed_ssl_backend_data *backend, - struct ssl_primary_config *conn_config) + struct ssl_filter_config *conn_config) { mbedtls_ssl_protocol_version ver_min = #ifdef MBEDTLS_SSL_PROTO_TLS1_2 @@ -442,7 +442,7 @@ static int mbed_verify_cb(void *ptr, mbedtls_x509_crt *crt, int depth, uint32_t *flags) { struct Curl_cfilter *cf = (struct Curl_cfilter *)ptr; - struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); + struct ssl_filter_config *conn_config = Curl_ssl_cf_get_filter_config(cf); struct Curl_easy *data = CF_DATA_CURRENT(cf); if(depth == 0) { @@ -479,7 +479,7 @@ static CURLcode mbed_load_cacert(struct Curl_cfilter *cf, struct ssl_connect_data *connssl = cf->ctx; struct mbed_ssl_backend_data *backend = (struct mbed_ssl_backend_data *)connssl->backend; - struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); + struct ssl_filter_config *conn_config = Curl_ssl_cf_get_filter_config(cf); const struct curl_blob *ca_info_blob = conn_config->ca_info_blob; const char * const ssl_cafile = /* CURLOPT_CAINFO_BLOB overrides CURLOPT_CAINFO */ @@ -487,8 +487,7 @@ static CURLcode mbed_load_cacert(struct Curl_cfilter *cf, const bool verifypeer = conn_config->verifypeer; const char * const ssl_capath = conn_config->CApath; #ifdef MBEDTLS_PEM_PARSE_C - struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); - const char * const ssl_cert_type = ssl_config->primary.cert_type; + const char * const ssl_cert_type = conn_config->cert_type; #endif int ret = -1; char errorbuf[128]; @@ -579,11 +578,11 @@ static CURLcode mbed_load_clicert(struct Curl_cfilter *cf, struct ssl_connect_data *connssl = cf->ctx; struct mbed_ssl_backend_data *backend = (struct mbed_ssl_backend_data *)connssl->backend; - struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); - char * const ssl_cert = ssl_config->primary.clientcert; - const struct curl_blob *ssl_cert_blob = ssl_config->primary.cert_blob; + struct ssl_filter_config *conn_config = Curl_ssl_cf_get_filter_config(cf); + char * const ssl_cert = conn_config->clientcert; + const struct curl_blob *ssl_cert_blob = conn_config->cert_blob; #ifdef MBEDTLS_PEM_PARSE_C - const char * const ssl_cert_type = ssl_config->primary.cert_type; + const char * const ssl_cert_type = conn_config->cert_type; #endif int ret = -1; char errorbuf[128]; @@ -658,18 +657,18 @@ static CURLcode mbed_load_privkey(struct Curl_cfilter *cf, struct ssl_connect_data *connssl = cf->ctx; struct mbed_ssl_backend_data *backend = (struct mbed_ssl_backend_data *)connssl->backend; - struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); + struct ssl_filter_config *conn_config = Curl_ssl_cf_get_filter_config(cf); int ret = -1; char errorbuf[128]; mbedtls_pk_init(&backend->pk); - if(ssl_config->primary.key || ssl_config->primary.key_blob) { - if(ssl_config->primary.key) { + if(conn_config->key || conn_config->key_blob) { + if(conn_config->key) { #ifdef MBEDTLS_FS_IO #if MBEDTLS_VERSION_NUMBER >= 0x04000000 - ret = mbedtls_pk_parse_keyfile(&backend->pk, ssl_config->primary.key, - ssl_config->primary.key_passwd); + ret = mbedtls_pk_parse_keyfile(&backend->pk, conn_config->key, + conn_config->key_passwd); if(ret == 0 && !(mbedtls_pk_can_do_psa(&backend->pk, PSA_ALG_RSA_PKCS1V15_SIGN(PSA_ALG_ANY_HASH), @@ -679,8 +678,8 @@ static CURLcode mbed_load_privkey(struct Curl_cfilter *cf, PSA_KEY_USAGE_SIGN_HASH))) ret = MBEDTLS_ERR_PK_TYPE_MISMATCH; #else - ret = mbedtls_pk_parse_keyfile(&backend->pk, ssl_config->primary.key, - ssl_config->primary.key_passwd, + ret = mbedtls_pk_parse_keyfile(&backend->pk, conn_config->key, + conn_config->key_passwd, mbedtls_ctr_drbg_random, &rng.drbg); if(ret == 0 && !(mbedtls_pk_can_do(&backend->pk, MBEDTLS_PK_RSA) || @@ -691,7 +690,7 @@ static CURLcode mbed_load_privkey(struct Curl_cfilter *cf, if(ret) { mbedtls_strerror(ret, errorbuf, sizeof(errorbuf)); failf(data, "mbedTLS: error reading private key %s: (-0x%04X) %s", - ssl_config->primary.key, (unsigned int)-ret, errorbuf); + conn_config->key, (unsigned int)-ret, errorbuf); return CURLE_SSL_CERTPROBLEM; } #else @@ -700,8 +699,8 @@ static CURLcode mbed_load_privkey(struct Curl_cfilter *cf, #endif } else { - const struct curl_blob *ssl_key_blob = ssl_config->primary.key_blob; - const char *passwd = ssl_config->primary.key_passwd; + const struct curl_blob *ssl_key_blob = conn_config->key_blob; + const char *passwd = conn_config->key_passwd; /* Unfortunately, mbedtls_pk_parse_key() requires the data to be null-terminated if the data is PEM encoded (even when provided the exact length). */ @@ -752,8 +751,8 @@ static CURLcode mbed_load_crl(struct Curl_cfilter *cf, struct ssl_connect_data *connssl = cf->ctx; struct mbed_ssl_backend_data *backend = (struct mbed_ssl_backend_data *)connssl->backend; - struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); - const char * const ssl_crlfile = ssl_config->primary.CRLfile; + struct ssl_filter_config *conn_config = Curl_ssl_cf_get_filter_config(cf); + const char * const ssl_crlfile = conn_config->CRLfile; #ifdef MBEDTLS_X509_CRL_PARSE_C mbedtls_x509_crl_init(&backend->crl); @@ -825,8 +824,7 @@ static CURLcode mbed_configure_ssl(struct Curl_cfilter *cf, struct ssl_connect_data *connssl = cf->ctx; struct mbed_ssl_backend_data *backend = (struct mbed_ssl_backend_data *)connssl->backend; - struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); - struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); + struct ssl_filter_config *conn_config = Curl_ssl_cf_get_filter_config(cf); int ret; bool session_applied = FALSE; CURLcode result; @@ -982,7 +980,7 @@ static CURLcode mbed_configure_ssl(struct Curl_cfilter *cf, #endif ); - if(ssl_config->primary.key || ssl_config->primary.key_blob) { + if(conn_config->key || conn_config->key_blob) { mbedtls_ssl_conf_own_cert(&backend->config, &backend->clicert, &backend->pk); } @@ -1032,7 +1030,7 @@ static CURLcode mbed_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data) { struct ssl_connect_data *connssl = cf->ctx; - struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); + struct ssl_filter_config *conn_config = Curl_ssl_cf_get_filter_config(cf); CURLcode result; if((conn_config->version == CURL_SSLVERSION_SSLv2) || diff --git a/lib/vtls/openssl.c b/lib/vtls/openssl.c index fcb39d061153..a874df997e4d 100644 --- a/lib/vtls/openssl.c +++ b/lib/vtls/openssl.c @@ -2540,7 +2540,7 @@ static CURLcode ossl_set_ssl_version_min_max(struct Curl_cfilter *cf, SSL_CTX *ctx, unsigned int ssl_version_min) { - struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); + struct ssl_filter_config *conn_config = Curl_ssl_cf_get_filter_config(cf); /* first, TLS min version... */ long curl_ssl_version_min = (long)ssl_version_min; long curl_ssl_version_max; @@ -2973,7 +2973,7 @@ static CURLcode ossl_load_trust_anchors(struct Curl_cfilter *cf, struct ossl_ctx *octx, X509_STORE *store) { - struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); + struct ssl_filter_config *conn_config = Curl_ssl_cf_get_filter_config(cf); CURLcode result = CURLE_OK; const char * const ssl_cafile = /* CURLOPT_CAINFO_BLOB overrides CURLOPT_CAINFO */ @@ -3083,11 +3083,10 @@ static CURLcode ossl_populate_x509_store(struct Curl_cfilter *cf, struct ossl_ctx *octx, X509_STORE *store) { - struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); - struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); + struct ssl_filter_config *conn_config = Curl_ssl_cf_get_filter_config(cf); CURLcode result = CURLE_OK; X509_LOOKUP *lookup = NULL; - const char * const ssl_crlfile = ssl_config->primary.CRLfile; + const char * const ssl_crlfile = conn_config->CRLfile; unsigned long x509flags = 0; CURL_TRC_CF(data, cf, "configuring OpenSSL's x509 trust store"); @@ -3133,7 +3132,7 @@ static CURLcode ossl_populate_x509_store(struct Curl_cfilter *cf, */ x509flags |= X509_V_FLAG_TRUSTED_FIRST; - if(!ssl_config->no_partialchain && !ssl_crlfile) { + if(!conn_config->no_partialchain && !ssl_crlfile) { /* Have intermediate certificates in the trust store be treated as trust-anchors, in the same way as self-signed root CA certificates are. This allows users to verify servers using the intermediate cert only, @@ -3190,10 +3189,9 @@ static bool ossl_cached_x509_store_different(struct Curl_cfilter *cf, const struct Curl_easy *data, const struct ossl_x509_share *mb) { - struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); - struct ssl_config_data *ssl_config = - Curl_ssl_cf_get_config(cf, CURL_UNCONST(data)); - if(mb->no_partialchain != ssl_config->no_partialchain) + struct ssl_filter_config *conn_config = Curl_ssl_cf_get_filter_config(cf); + (void)data; + if(mb->no_partialchain != conn_config->no_partialchain) return TRUE; if(!mb->CAfile || !conn_config->CAfile) return mb->CAfile != conn_config->CAfile; @@ -3228,7 +3226,7 @@ static void ossl_set_cached_x509_store(struct Curl_cfilter *cf, X509_STORE *store, bool is_empty) { - struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); + struct ssl_filter_config *conn_config = Curl_ssl_cf_get_filter_config(cf); struct Curl_multi *multi = data->multi; struct ossl_x509_share *share; @@ -3254,8 +3252,6 @@ static void ossl_set_cached_x509_store(struct Curl_cfilter *cf, if(X509_STORE_up_ref(store)) { char *CAfile = NULL; - struct ssl_config_data *ssl_config = - Curl_ssl_cf_get_config(cf, CURL_UNCONST(data)); if(conn_config->CAfile) { CAfile = curlx_strdup(conn_config->CAfile); @@ -3274,7 +3270,7 @@ static void ossl_set_cached_x509_store(struct Curl_cfilter *cf, share->store = store; share->store_is_empty = is_empty; share->CAfile = CAfile; - share->no_partialchain = ssl_config->no_partialchain; + share->no_partialchain = conn_config->no_partialchain; } } @@ -3282,8 +3278,7 @@ CURLcode Curl_ssl_setup_x509_store(struct Curl_cfilter *cf, struct Curl_easy *data, struct ossl_ctx *octx) { - struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); - struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); + struct ssl_filter_config *conn_config = Curl_ssl_cf_get_filter_config(cf); CURLcode result = CURLE_OK; X509_STORE *cached_store; bool cache_criteria_met, is_empty; @@ -3295,7 +3290,7 @@ CURLcode Curl_ssl_setup_x509_store(struct Curl_cfilter *cf, conn_config->verifypeer && !conn_config->CApath && !conn_config->ca_info_blob && - !ssl_config->primary.CRLfile && + !conn_config->CRLfile && !conn_config->native_ca_store; ERR_set_mark(); @@ -3327,8 +3322,7 @@ static bool ossl_apply_session( Curl_ossl_init_session_reuse_cb *sess_reuse_cb, struct Curl_ssl_session *scs) { - struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); - struct ssl_primary_config *conn_cfg = Curl_ssl_cf_get_primary_config(cf); + struct ssl_filter_config *conn_cfg = Curl_ssl_cf_get_filter_config(cf); const unsigned char *der_sessionid = scs->sdata; size_t der_sessionid_size = scs->sdata_len; SSL_SESSION *ssl_session = NULL; @@ -3367,7 +3361,7 @@ static bool ossl_apply_session( infof(data, "SSL verify result: %lx", (unsigned long)SSL_get_verify_result(octx->ssl)); #ifdef HAVE_OPENSSL_EARLYDATA - if(ssl_config->earlydata && scs->alpn && + if(conn_cfg->earlydata && scs->alpn && SSL_SESSION_get_max_early_data(ssl_session) && !cf->conn->bits.connect_only && (SSL_version(octx->ssl) == TLS1_3_VERSION)) { @@ -3382,7 +3376,7 @@ static bool ossl_apply_session( } #else (void)alpns; - (void)ssl_config; + (void)conn_cfg; (void)sess_reuse_cb; #endif } @@ -3403,7 +3397,7 @@ static CURLcode ossl_init_session_and_alpns( const struct alpn_spec *alpns_requested, Curl_ossl_init_session_reuse_cb *sess_reuse_cb) { - struct ssl_primary_config *conn_cfg = Curl_ssl_cf_get_primary_config(cf); + struct ssl_filter_config *conn_cfg = Curl_ssl_cf_get_filter_config(cf); struct alpn_spec alpns; CURLcode result; @@ -3610,7 +3604,7 @@ static CURLcode ossl_init_ssl(struct ossl_ctx *octx, SSL_set_app_data(octx->ssl, ssl_user_data); #ifndef OPENSSL_NO_OCSP - if(Curl_ssl_cf_get_primary_config(cf)->verifystatus) + if(Curl_ssl_cf_get_filter_config(cf)->verifystatus) SSL_set_tlsext_status_type(octx->ssl, TLSEXT_STATUSTYPE_ocsp); #endif @@ -3641,7 +3635,7 @@ static CURLcode ossl_init_method(struct Curl_cfilter *cf, const SSL_METHOD **pmethod, unsigned int *pssl_version_min) { - struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); + struct ssl_filter_config *conn_config = Curl_ssl_cf_get_filter_config(cf); *pmethod = NULL; *pssl_version_min = conn_config->version; @@ -3703,11 +3697,11 @@ CURLcode Curl_ossl_ctx_init(struct ossl_ctx *octx, const char *ciphers; const SSL_METHOD *req_method = NULL; ctx_option_t ctx_options = 0; - struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); - struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); - char * const ssl_cert = ssl_config->primary.clientcert; - const struct curl_blob *ssl_cert_blob = ssl_config->primary.cert_blob; - const char * const ssl_cert_type = ssl_config->primary.cert_type; + struct ssl_filter_config *conn_config = Curl_ssl_cf_get_filter_config(cf); + struct ssl_easy_config *ssl_config = Curl_ssl_cf_get_easy_config(cf, data); + char * const ssl_cert = conn_config->clientcert; + const struct curl_blob *ssl_cert_blob = conn_config->cert_blob; + const char * const ssl_cert_type = conn_config->cert_type; unsigned int ssl_version_min; char error_buffer[256]; @@ -3799,7 +3793,7 @@ CURLcode Curl_ossl_ctx_init(struct ossl_ctx *octx, /* unless the user explicitly asks to allow the protocol vulnerability we use the workaround */ - if(!ssl_config->enable_beast) + if(!conn_config->enable_beast) ctx_options &= ~(ctx_option_t)SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS; DEBUGASSERT(ssl_version_min != CURL_SSLVERSION_DEFAULT); @@ -3878,9 +3872,9 @@ CURLcode Curl_ossl_ctx_init(struct ossl_ctx *octx, if(ssl_cert || ssl_cert_blob || ssl_cert_type) { result = client_cert(data, octx->ssl_ctx, ssl_cert, ssl_cert_blob, ssl_cert_type, - ssl_config->primary.key, ssl_config->primary.key_blob, - ssl_config->primary.key_type, - ssl_config->primary.key_passwd); + conn_config->key, conn_config->key_blob, + conn_config->key_type, + conn_config->key_passwd); if(result) /* failf() is already done in client_cert() */ return result; @@ -4137,7 +4131,7 @@ static CURLcode ossl_connect_step2(struct Curl_cfilter *cf, int err; struct ssl_connect_data *connssl = cf->ctx; struct ossl_ctx *octx = (struct ossl_ctx *)connssl->backend; - struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); + struct ssl_easy_config *ssl_config = Curl_ssl_cf_get_easy_config(cf, data); DEBUGASSERT(connssl->connecting_state == ssl_connect_2); DEBUGASSERT(octx); @@ -4324,8 +4318,8 @@ static CURLcode ossl_connect_step2(struct Curl_cfilter *cf, VERBOSE(status = "bad call (unexpected)"); break; case SSL_ECH_STATUS_BAD_NAME: { - struct ssl_primary_config *conn_config = - Curl_ssl_cf_get_primary_config(cf); + struct ssl_filter_config *conn_config = + Curl_ssl_cf_get_filter_config(cf); if(!conn_config->verifypeer && !conn_config->verifyhost && inner && !strcmp(inner, connssl->peer.origin->hostname)) { VERBOSE(status = "bad name (tolerated without peer verification)"); @@ -4511,7 +4505,7 @@ static CURLcode ossl_check_issuer(struct Curl_cfilter *cf, struct Curl_easy *data, X509 *server_cert) { - struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); + struct ssl_filter_config *conn_config = Curl_ssl_cf_get_filter_config(cf); X509 *issuer = NULL; BIO *fp = NULL; char err_buf[256] = ""; @@ -4700,7 +4694,7 @@ static CURLcode ossl_apple_verify(struct Curl_cfilter *cf, struct ossl_ctx *octx, struct ssl_peer *peer) { - struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); + struct ssl_filter_config *conn_config = Curl_ssl_cf_get_filter_config(cf); struct ossl_certs_ctx chain; CURLcode result; @@ -4766,8 +4760,8 @@ CURLcode Curl_ossl_check_peer_cert(struct Curl_cfilter *cf, struct ssl_peer *peer) { struct connectdata *conn = cf->conn; - struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); - struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); + struct ssl_easy_config *ssl_config = Curl_ssl_cf_get_easy_config(cf, data); + struct ssl_filter_config *conn_config = Curl_ssl_cf_get_filter_config(cf); CURLcode result = CURLE_OK; long ossl_verify; X509 *server_cert; diff --git a/lib/vtls/rustls.c b/lib/vtls/rustls.c index 330ea29f1fe7..8557c6953870 100644 --- a/lib/vtls/rustls.c +++ b/lib/vtls/rustls.c @@ -526,7 +526,7 @@ static void cr_keylog_log_cb(struct rustls_str label, static CURLcode init_config_builder(struct Curl_easy *data, - const struct ssl_primary_config *conn_config, + const struct ssl_filter_config *conn_config, struct rustls_client_config_builder **config_builder) { const struct rustls_supported_ciphersuite **cipher_suites = NULL; @@ -676,7 +676,7 @@ init_config_builder_alpn(struct Curl_easy *data, static CURLcode init_config_builder_verifier_crl( struct Curl_easy *data, - const struct ssl_primary_config *conn_config, + const struct ssl_filter_config *conn_config, struct rustls_web_pki_server_cert_verifier_builder *builder) { CURLcode result = CURLE_OK; @@ -708,7 +708,7 @@ static CURLcode init_config_builder_verifier_crl( static CURLcode init_config_builder_verifier(struct Curl_easy *data, struct rustls_client_config_builder *builder, - const struct ssl_primary_config *conn_config, + const struct ssl_filter_config *conn_config, const struct curl_blob *ca_info_blob, const char * const ssl_cafile) { @@ -841,8 +841,7 @@ init_config_builder_keylog(struct Curl_easy *data, static CURLcode init_config_builder_client_auth(struct Curl_easy *data, - const struct ssl_primary_config *conn_config, - const struct ssl_config_data *ssl_config, + const struct ssl_filter_config *conn_config, struct rustls_client_config_builder *builder) { struct dynbuf cert_contents; @@ -851,14 +850,14 @@ init_config_builder_client_auth(struct Curl_easy *data, const struct rustls_certified_key *certified_key = NULL; CURLcode result = CURLE_OK; - if(conn_config->clientcert && !ssl_config->primary.key) { + if(conn_config->clientcert && !conn_config->key) { failf(data, "rustls: must provide key with certificate '%s'", conn_config->clientcert); return CURLE_SSL_CERTPROBLEM; } - else if(!conn_config->clientcert && ssl_config->primary.key) { + else if(!conn_config->clientcert && conn_config->key) { failf(data, "rustls: must provide certificate with key '%s'", - ssl_config->primary.key); + conn_config->key); return CURLE_SSL_CERTPROBLEM; } @@ -872,9 +871,9 @@ init_config_builder_client_auth(struct Curl_easy *data, goto cleanup; } - if(!read_file_into(ssl_config->primary.key, &key_contents)) { + if(!read_file_into(conn_config->key, &key_contents)) { failf(data, "rustls: failed to read key file: '%s'", - ssl_config->primary.key); + conn_config->key); result = CURLE_SSL_CERTPROBLEM; goto cleanup; } @@ -1020,9 +1019,8 @@ static CURLcode cr_init_backend(struct Curl_cfilter *cf, struct rustls_ssl_backend_data * const backend) { const struct ssl_connect_data *connssl = cf->ctx; - const struct ssl_primary_config *conn_config = - Curl_ssl_cf_get_primary_config(cf); - struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); + const struct ssl_filter_config *conn_config = + Curl_ssl_cf_get_filter_config(cf); struct rustls_connection *rconn = NULL; struct rustls_client_config_builder *config_builder = NULL; @@ -1074,10 +1072,9 @@ static CURLcode cr_init_backend(struct Curl_cfilter *cf, } } - if(conn_config->clientcert || ssl_config->primary.key) { + if(conn_config->clientcert || conn_config->key) { result = init_config_builder_client_auth(data, conn_config, - ssl_config, config_builder); if(result != CURLE_OK) { rustls_client_config_builder_free(config_builder); diff --git a/lib/vtls/schannel.c b/lib/vtls/schannel.c index 251dd7a41749..c5713b6f5874 100644 --- a/lib/vtls/schannel.c +++ b/lib/vtls/schannel.c @@ -166,7 +166,7 @@ static CURLcode schannel_set_ssl_version_min_max(DWORD *enabled_protocols, struct Curl_cfilter *cf, struct Curl_easy *data) { - struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); + struct ssl_filter_config *conn_config = Curl_ssl_cf_get_filter_config(cf); long ssl_version = conn_config->version; long ssl_version_max = (long)conn_config->version_max; long i = ssl_version; @@ -381,7 +381,7 @@ static CURLcode get_client_cert(struct Curl_cfilter *cf, HCERTSTORE *out_cert_store, PCCERT_CONTEXT *out_cert_context) { - struct ssl_primary_config *sslc = Curl_ssl_cf_get_primary_config(cf); + struct ssl_filter_config *sslc = Curl_ssl_cf_get_filter_config(cf); PCCERT_CONTEXT client_cert = NULL; HCERTSTORE client_cert_store = NULL; CURLcode result = CURLE_OK; @@ -605,7 +605,7 @@ static CURLcode acquire_sspi_handle(struct Curl_cfilter *cf, DWORD flags, DWORD enabled_protocols) { - struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); + struct ssl_filter_config *conn_config = Curl_ssl_cf_get_filter_config(cf); SECURITY_STATUS sspi_status = SEC_E_OK; CURLcode result; @@ -724,8 +724,7 @@ static CURLcode schannel_acquire_credential_handle(struct Curl_cfilter *cf, struct Curl_easy *data) { struct ssl_connect_data *connssl = cf->ctx; - struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); - struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); + struct ssl_filter_config *conn_config = Curl_ssl_cf_get_filter_config(cf); PCCERT_CONTEXT client_cert = NULL; HCERTSTORE client_cert_store = NULL; @@ -746,14 +745,14 @@ static CURLcode schannel_acquire_credential_handle(struct Curl_cfilter *cf, else flags = SCH_CRED_AUTO_CRED_VALIDATION; - if(ssl_config->no_revoke) { + if(conn_config->no_revoke) { flags |= SCH_CRED_IGNORE_NO_REVOCATION_CHECK | SCH_CRED_IGNORE_REVOCATION_OFFLINE; DEBUGF(infof(data, "schannel: disabled server certificate revocation " "checks")); } - else if(ssl_config->revoke_best_effort) { + else if(conn_config->revoke_best_effort) { flags |= SCH_CRED_IGNORE_NO_REVOCATION_CHECK | SCH_CRED_IGNORE_REVOCATION_OFFLINE | SCH_CRED_REVOCATION_CHECK_CHAIN; @@ -849,7 +848,7 @@ static CURLcode schannel_connect_step1(struct Curl_cfilter *cf, struct ssl_connect_data *connssl = cf->ctx; struct schannel_ssl_backend_data *backend = (struct schannel_ssl_backend_data *)connssl->backend; - struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); + struct ssl_filter_config *conn_config = Curl_ssl_cf_get_filter_config(cf); SecBuffer outbuf; SecBufferDesc outbuf_desc; SecBuffer inbuf; @@ -1229,7 +1228,7 @@ static CURLcode schannel_connect_step2(struct Curl_cfilter *cf, struct ssl_connect_data *connssl = cf->ctx; struct schannel_ssl_backend_data *backend = (struct schannel_ssl_backend_data *)connssl->backend; - struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); + struct ssl_filter_config *conn_config = Curl_ssl_cf_get_filter_config(cf); int i; size_t nread = 0; SecBuffer outbuf[3]; @@ -1587,7 +1586,7 @@ static CURLcode schannel_connect_step3(struct Curl_cfilter *cf, struct ssl_connect_data *connssl = cf->ctx; struct schannel_ssl_backend_data *backend = (struct schannel_ssl_backend_data *)connssl->backend; - struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); + struct ssl_easy_config *ssl_config = Curl_ssl_cf_get_easy_config(cf, data); CURLcode result = CURLE_OK; SECURITY_STATUS sspi_status = SEC_E_OK; CERT_CONTEXT *ccert_context = NULL; @@ -2723,7 +2722,7 @@ static void *schannel_get_internals(struct ssl_connect_data *connssl, HCERTSTORE Curl_schannel_get_cached_cert_store(struct Curl_cfilter *cf, struct Curl_easy *data) { - struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); + struct ssl_filter_config *conn_config = Curl_ssl_cf_get_filter_config(cf); struct Curl_multi *multi = data->multi; const struct curl_blob *ca_info_blob = conn_config->ca_info_blob; struct schannel_cert_share *share; @@ -2802,7 +2801,7 @@ bool Curl_schannel_set_cached_cert_store(struct Curl_cfilter *cf, struct Curl_easy *data, HCERTSTORE cert_store) { - struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); + struct ssl_filter_config *conn_config = Curl_ssl_cf_get_filter_config(cf); struct Curl_multi *multi = data->multi; const struct curl_blob *ca_info_blob = conn_config->ca_info_blob; struct schannel_cert_share *share; diff --git a/lib/vtls/schannel_verify.c b/lib/vtls/schannel_verify.c index bcac42ab5564..3bc4be9e779b 100644 --- a/lib/vtls/schannel_verify.c +++ b/lib/vtls/schannel_verify.c @@ -631,8 +631,7 @@ CURLcode Curl_verify_certificate(struct Curl_cfilter *cf, struct Curl_easy *data) { struct ssl_connect_data *connssl = cf->ctx; - struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); - struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); + struct ssl_filter_config *conn_config = Curl_ssl_cf_get_filter_config(cf); SECURITY_STATUS sspi_status; CURLcode result = CURLE_OK; CERT_CONTEXT *pCertContextServer = NULL; @@ -724,7 +723,7 @@ CURLcode Curl_verify_certificate(struct Curl_cfilter *cf, /* Win8/Server2012 allows us to match partial chains */ if(curlx_verify_windows_version(6, 2, 0, PLATFORM_WINNT, VERSION_GREATER_THAN_EQUAL) && - !ssl_config->no_partialchain) { + !conn_config->no_partialchain) { engine_config.cbSize = sizeof(engine_config); engine_config.dwExclusiveFlags = CERT_CHAIN_EXCLUSIVE_ENABLE_CA_FLAG; } @@ -764,7 +763,7 @@ CURLcode Curl_verify_certificate(struct Curl_cfilter *cf, NULL, pCertContextServer->hCertStore, &ChainPara, - (ssl_config->no_revoke ? 0 : + (conn_config->no_revoke ? 0 : CERT_CHAIN_REVOCATION_CHECK_CHAIN), NULL, &pChainContext)) { @@ -780,7 +779,7 @@ CURLcode Curl_verify_certificate(struct Curl_cfilter *cf, DWORD dwTrustErrorMask = ~(DWORD)(CERT_TRUST_IS_NOT_TIME_NESTED); dwTrustErrorMask &= pSimpleChain->TrustStatus.dwErrorStatus; - if(ssl_config->revoke_best_effort) { + if(conn_config->revoke_best_effort) { /* Ignore errors when root certificates are missing the revocation * list URL, or when the list could not be downloaded because the * server is currently unreachable. */ diff --git a/lib/vtls/vtls.c b/lib/vtls/vtls.c index da739f0e458c..9d95ebb020dc 100644 --- a/lib/vtls/vtls.c +++ b/lib/vtls/vtls.c @@ -160,22 +160,23 @@ int Curl_ssl_init(void) return 1; } -static bool ssl_prefs_check(struct Curl_easy *data) +static bool ssl_prefs_check(struct Curl_cfilter *cf, + struct Curl_easy *data) { + struct ssl_filter_config *conn_config = Curl_ssl_cf_get_filter_config(cf); /* check for CURLOPT_SSLVERSION invalid parameter value */ - const unsigned char sslver = data->set.ssl.primary.version; - if(sslver >= CURL_SSLVERSION_LAST) { + if(conn_config->version >= CURL_SSLVERSION_LAST) { failf(data, "Unrecognized parameter value passed via CURLOPT_SSLVERSION"); return FALSE; } - switch(data->set.ssl.primary.version_max) { + switch(conn_config->version_max) { case CURL_SSLVERSION_MAX_NONE: case CURL_SSLVERSION_MAX_DEFAULT: break; default: - if((data->set.ssl.primary.version_max >> 16) < sslver) { + if((conn_config->version_max >> 16) < conn_config->version) { failf(data, "CURL_SSLVERSION_MAX incompatible with CURL_SSLVERSION"); return FALSE; } @@ -896,7 +897,7 @@ static ssl_peer_type get_peer_type(const char *hostname) CURLcode Curl_ssl_peer_init(struct ssl_peer *ssl_peer, struct Curl_peer *origin, struct Curl_peer *peer, - struct ssl_primary_config *sslc, + struct ssl_filter_config *sslc, const char *tls_id, uint8_t transport) { @@ -986,7 +987,7 @@ static CURLcode ssl_cf_connect(struct Curl_cfilter *cf, *done = FALSE; if(!connssl->prefs_checked) { - if(!ssl_prefs_check(data)) { + if(!ssl_prefs_check(cf, data)) { result = CURLE_SSL_CONNECT_ERROR; goto out; } @@ -1383,7 +1384,7 @@ static CURLcode cf_ssl_create(struct Curl_cfilter **pcf, static CURLcode cf_ssl_peer_init(struct Curl_cfilter *cf, struct Curl_peer *origin, struct Curl_peer *peer, - struct ssl_primary_config *sslc) + struct ssl_filter_config *sslc) { struct ssl_connect_data *connssl = cf->ctx; char tls_id[80]; @@ -1586,8 +1587,9 @@ bool Curl_ssl_cf_is_proxy(struct Curl_cfilter *cf) return (cf->cft->flags & CF_TYPE_SSL) && (cf->cft->flags & CF_TYPE_PROXY); } -struct ssl_config_data *Curl_ssl_cf_get_config(struct Curl_cfilter *cf, - struct Curl_easy *data) +struct ssl_easy_config * +Curl_ssl_cf_get_easy_config(struct Curl_cfilter *cf, + struct Curl_easy *data) { #ifdef CURL_DISABLE_PROXY (void)cf; @@ -1597,8 +1599,8 @@ struct ssl_config_data *Curl_ssl_cf_get_config(struct Curl_cfilter *cf, #endif } -struct ssl_primary_config *Curl_ssl_cf_get_primary_config( - struct Curl_cfilter *cf) +struct ssl_filter_config * +Curl_ssl_cf_get_filter_config(struct Curl_cfilter *cf) { #ifdef CURL_DISABLE_PROXY return &cf->conn->ssl_config; diff --git a/lib/vtls/vtls.h b/lib/vtls/vtls.h index 8ee22c8d5aaa..8aed5ddb6b59 100644 --- a/lib/vtls/vtls.h +++ b/lib/vtls/vtls.h @@ -26,8 +26,8 @@ #include "curl_setup.h" struct connectdata; -struct ssl_config_data; -struct ssl_primary_config; +struct ssl_easy_config; +struct ssl_filter_config; struct Curl_cfilter; struct Curl_easy; struct dynbuf; @@ -110,7 +110,7 @@ curl_sslbackend Curl_ssl_backend(void); CURLcode Curl_ssl_peer_init(struct ssl_peer *ssl_peer, struct Curl_peer *origin, struct Curl_peer *peer, - struct ssl_primary_config *sslc, + struct ssl_filter_config *sslc, const char *tls_id, uint8_t transport); /** @@ -202,17 +202,14 @@ CURLcode Curl_cf_ssl_proxy_insert_after(struct Curl_cfilter *cf_at, */ bool Curl_ssl_supports(struct Curl_easy *data, unsigned int ssl_option); -/** - * Get the ssl_config_data in `data` that is relevant for cfilter `cf`. - */ -struct ssl_config_data *Curl_ssl_cf_get_config(struct Curl_cfilter *cf, - struct Curl_easy *data); +/* Get the ssl_easy_config from `data` relevant for cfilter `cf`. */ +struct ssl_easy_config * +Curl_ssl_cf_get_easy_config(struct Curl_cfilter *cf, + struct Curl_easy *data); -/** - * Get the primary config relevant for the filter from its connection. - */ -struct ssl_primary_config *Curl_ssl_cf_get_primary_config( - struct Curl_cfilter *cf); +/* Get the filter config relevant for cfilter `cf`. */ +struct ssl_filter_config * +Curl_ssl_cf_get_filter_config(struct Curl_cfilter *cf); extern struct Curl_cftype Curl_cft_ssl; #ifndef CURL_DISABLE_PROXY @@ -234,8 +231,8 @@ extern struct Curl_cftype Curl_cft_ssl_proxy; #define Curl_ssl_supports(a, b) FALSE #define Curl_ssl_cfilter_add(a, b, c, d) CURLE_NOT_BUILT_IN #define Curl_ssl_cfilter_remove(a, b, c) CURLE_OK -#define Curl_ssl_cf_get_config(a, b) NULL -#define Curl_ssl_cf_get_primary_config(a) NULL +#define Curl_ssl_cf_get_easy_config(a, b) NULL +#define Curl_ssl_cf_get_filter_config(a) NULL #endif #endif /* HEADER_CURL_VTLS_H */ diff --git a/lib/vtls/vtls_config.c b/lib/vtls/vtls_config.c index 932106450d3b..df0372366c86 100644 --- a/lib/vtls/vtls_config.c +++ b/lib/vtls/vtls_config.c @@ -97,7 +97,7 @@ static bool blobcmp(struct curl_blob *first, struct curl_blob *second) return !memcmp(first->data, second->data, first->len); /* same data */ } -void Curl_ssl_config_init(struct ssl_primary_config *sslc) +void Curl_ssl_config_init(struct ssl_easy_config *sslc) { /* * libcurl 7.10 introduced SSL verification *by default*! This needs to be @@ -108,7 +108,7 @@ void Curl_ssl_config_init(struct ssl_primary_config *sslc) sslc->cache_session = TRUE; /* caching by default */ } -void Curl_ssl_config_cleanup(struct ssl_primary_config *sslc) +void Curl_ssl_config_cleanup(struct ssl_filter_config *sslc) { if(sslc->deep_copy) { curlx_safefree(sslc->CApath); @@ -134,8 +134,8 @@ void Curl_ssl_config_cleanup(struct ssl_primary_config *sslc) } static bool match_ssl_primary_config(struct Curl_easy *data, - struct ssl_primary_config *c1, - struct ssl_primary_config *c2) + struct ssl_filter_config *c1, + struct ssl_filter_config *c2) { (void)data; if((c1->version == c2->version) && @@ -170,22 +170,23 @@ static bool match_ssl_primary_config(struct Curl_easy *data, } bool Curl_ssl_conn_config_match(struct Curl_easy *data, + struct ssl_filter_config *conn_config, struct connectdata *candidate, bool proxy) { #ifndef CURL_DISABLE_PROXY if(proxy) - return match_ssl_primary_config(data, &data->set.proxy_ssl.primary, + return match_ssl_primary_config(data, conn_config, &candidate->proxy_ssl_config); #else (void)proxy; #endif - return match_ssl_primary_config(data, &data->set.ssl.primary, + return match_ssl_primary_config(data, conn_config, &candidate->ssl_config); } -static bool clone_ssl_primary_config(struct ssl_primary_config *source, - struct ssl_primary_config *dest) +static bool clone_ssl_primary_config(struct ssl_filter_config *source, + struct ssl_filter_config *dest) { DEBUGASSERT(!dest->deep_copy); dest->deep_copy = TRUE; @@ -194,10 +195,15 @@ static bool clone_ssl_primary_config(struct ssl_primary_config *source, dest->verifypeer = source->verifypeer; dest->verifyhost = source->verifyhost; dest->verifystatus = source->verifystatus; + dest->ssl_options = source->ssl_options; dest->native_ca_store = source->native_ca_store; dest->cache_session = source->cache_session; - dest->ssl_options = source->ssl_options; dest->auto_client_cert = source->auto_client_cert; + dest->earlydata = source->earlydata; + dest->enable_beast = source->enable_beast; + dest->no_partialchain = source->no_partialchain; + dest->no_revoke = source->no_revoke; + dest->revoke_best_effort = source->revoke_best_effort; CLONE_BLOB(cert_blob); CLONE_BLOB(ca_info_blob); @@ -221,24 +227,25 @@ static bool clone_ssl_primary_config(struct ssl_primary_config *source, return TRUE; } -static void ssl_easy_config_compl_options(struct Curl_peer *origin, - struct Curl_peer *initial_origin, - struct ssl_config_data *sslc) +static void +ssl_easy_config_compl_options(struct Curl_peer *origin, + struct Curl_peer *initial_origin, + struct ssl_easy_config *sslc, + struct ssl_filter_config *primary) { - uint8_t options = sslc->primary.ssl_options; + uint8_t options = sslc->ssl_options; /* If set via CURLOPT_(PROXY_)SSL_OPTIONS, we definitely use it. * If not, we switch it on for supported backends if no custom * CA settings exist. */ - sslc->primary.native_ca_store = !!(options & CURLSSLOPT_NATIVE_CA); - sslc->primary.auto_client_cert = + primary->native_ca_store = !!(options & CURLSSLOPT_NATIVE_CA); + primary->earlydata = !!(options & CURLSSLOPT_EARLYDATA); + primary->auto_client_cert = Curl_peer_equal(origin, initial_origin) && !!(options & CURLSSLOPT_AUTO_CLIENT_CERT); - - sslc->enable_beast = !!(options & CURLSSLOPT_ALLOW_BEAST); - sslc->no_partialchain = !!(options & CURLSSLOPT_NO_PARTIALCHAIN); - sslc->no_revoke = !!(options & CURLSSLOPT_NO_REVOKE); - sslc->revoke_best_effort = !!(options & CURLSSLOPT_REVOKE_BEST_EFFORT); - sslc->earlydata = !!(options & CURLSSLOPT_EARLYDATA); + primary->enable_beast = !!(options & CURLSSLOPT_ALLOW_BEAST); + primary->no_partialchain = !!(options & CURLSSLOPT_NO_PARTIALCHAIN); + primary->no_revoke = !!(options & CURLSSLOPT_NO_REVOKE); + primary->revoke_best_effort = !!(options & CURLSSLOPT_REVOKE_BEST_EFFORT); } static char *ssl_easy_steal(struct Curl_easy *data, enum dupstring id) @@ -248,20 +255,23 @@ static char *ssl_easy_steal(struct Curl_easy *data, enum dupstring id) return CURL_UNCONST(CURL_EASY_STR(data, id)); } -CURLcode Curl_ssl_easy_config_complete(struct Curl_easy *data, - struct Curl_peer *origin) +CURLcode Curl_ssl_filter_config_tmp_init( + struct Curl_easy *data, struct Curl_peer *origin, + struct ssl_filter_config *ssl_origin, + struct ssl_filter_config *ssl_proxy) { - struct ssl_config_data *sslc = &data->set.ssl; + struct ssl_easy_config *sslc = &data->set.ssl; #if defined(CURL_CA_PATH) || defined(CURL_CA_BUNDLE) CURLcode result; #endif - ssl_easy_config_compl_options(origin, data->state.initial_origin, sslc); + ssl_easy_config_compl_options(origin, data->state.initial_origin, sslc, + ssl_origin); if(Curl_ssl_backend() != CURLSSLBACKEND_SCHANNEL) { #if defined(USE_APPLE_SECTRUST) || defined(CURL_CA_NATIVE) if(!sslc->custom_capath && !sslc->custom_cafile && !sslc->custom_cablob) - sslc->primary.native_ca_store = TRUE; + ssl_origin->native_ca_store = TRUE; #endif #ifdef CURL_CA_PATH if(!sslc->custom_capath && !CURL_EASY_STR(data, STRING_SSL_CAPATH)) { @@ -278,51 +288,59 @@ CURLcode Curl_ssl_easy_config_complete(struct Curl_easy *data, } #endif } - sslc->primary.CAfile = ssl_easy_steal(data, STRING_SSL_CAFILE); - sslc->primary.CRLfile = ssl_easy_steal(data, STRING_SSL_CRLFILE); - sslc->primary.CApath = ssl_easy_steal(data, STRING_SSL_CAPATH); - sslc->primary.cipher_list = ssl_easy_steal(data, STRING_SSL_CIPHER_LIST); - sslc->primary.cipher_list13 = ssl_easy_steal(data, STRING_SSL_CIPHER13_LIST); - sslc->primary.signature_algorithms = + + ssl_origin->version = sslc->version; + ssl_origin->version_max = sslc->version_max; + ssl_origin->verifypeer = sslc->verifypeer; + ssl_origin->verifyhost = sslc->verifyhost; + ssl_origin->verifystatus = sslc->verifystatus; + ssl_origin->cache_session = sslc->cache_session; + ssl_origin->ssl_options = sslc->ssl_options; + ssl_origin->CAfile = ssl_easy_steal(data, STRING_SSL_CAFILE); + ssl_origin->CRLfile = ssl_easy_steal(data, STRING_SSL_CRLFILE); + ssl_origin->CApath = ssl_easy_steal(data, STRING_SSL_CAPATH); + ssl_origin->cipher_list = ssl_easy_steal(data, STRING_SSL_CIPHER_LIST); + ssl_origin->cipher_list13 = ssl_easy_steal(data, STRING_SSL_CIPHER13_LIST); + ssl_origin->signature_algorithms = ssl_easy_steal(data, STRING_SSL_SIGNATURE_ALGORITHMS); - sslc->primary.ca_info_blob = data->set.blobs[BLOB_CAINFO]; - sslc->primary.curves = ssl_easy_steal(data, STRING_SSL_EC_CURVES); + ssl_origin->ca_info_blob = data->set.blobs[BLOB_CAINFO]; + ssl_origin->curves = ssl_easy_steal(data, STRING_SSL_EC_CURVES); /* Maybe these should not be used for another origin. But for * backwards compatibility, keep them in. */ - sslc->primary.issuercert = ssl_easy_steal(data, STRING_SSL_ISSUERCERT); - sslc->primary.issuercert_blob = data->set.blobs[BLOB_SSL_ISSUERCERT]; + ssl_origin->issuercert = ssl_easy_steal(data, STRING_SSL_ISSUERCERT); + ssl_origin->issuercert_blob = data->set.blobs[BLOB_SSL_ISSUERCERT]; if(Curl_peer_equal(data->state.initial_origin, origin)) { - sslc->primary.pinned_key = + ssl_origin->pinned_key = ssl_easy_steal(data, STRING_SSL_PINNEDPUBLICKEY); - sslc->primary.cert_blob = data->set.blobs[BLOB_CERT]; - sslc->primary.cert_type = ssl_easy_steal(data, STRING_CERT_TYPE); - sslc->primary.key = ssl_easy_steal(data, STRING_KEY); - sslc->primary.key_type = ssl_easy_steal(data, STRING_KEY_TYPE); - sslc->primary.key_passwd = ssl_easy_steal(data, STRING_KEY_PASSWD); - sslc->primary.clientcert = ssl_easy_steal(data, STRING_CERT); - sslc->primary.key_blob = data->set.blobs[BLOB_KEY]; + ssl_origin->cert_blob = data->set.blobs[BLOB_CERT]; + ssl_origin->cert_type = ssl_easy_steal(data, STRING_CERT_TYPE); + ssl_origin->key = ssl_easy_steal(data, STRING_KEY); + ssl_origin->key_type = ssl_easy_steal(data, STRING_KEY_TYPE); + ssl_origin->key_passwd = ssl_easy_steal(data, STRING_KEY_PASSWD); + ssl_origin->clientcert = ssl_easy_steal(data, STRING_CERT); + ssl_origin->key_blob = data->set.blobs[BLOB_KEY]; } else { - sslc->primary.pinned_key = NULL; - sslc->primary.cert_blob = NULL; - sslc->primary.cert_type = NULL; - sslc->primary.key = NULL; - sslc->primary.key_type = NULL; - sslc->primary.key_passwd = NULL; - sslc->primary.clientcert = NULL; - sslc->primary.key_blob = NULL; + ssl_origin->pinned_key = NULL; + ssl_origin->cert_blob = NULL; + ssl_origin->cert_type = NULL; + ssl_origin->key = NULL; + ssl_origin->key_type = NULL; + ssl_origin->key_passwd = NULL; + ssl_origin->clientcert = NULL; + ssl_origin->key_blob = NULL; } #ifndef CURL_DISABLE_PROXY sslc = &data->set.proxy_ssl; - /* no initial origin for proxy, it is not changed for redirects */ - ssl_easy_config_compl_options(NULL, NULL, sslc); - if(Curl_ssl_backend() != CURLSSLBACKEND_SCHANNEL) { + ssl_easy_config_compl_options(NULL, NULL, sslc, ssl_proxy); + if((Curl_ssl_backend() != CURLSSLBACKEND_SCHANNEL)) { + /* no initial origin for proxy, it is not changed for redirects */ #if defined(USE_APPLE_SECTRUST) || defined(CURL_CA_NATIVE) if(!sslc->custom_capath && !sslc->custom_cafile && !sslc->custom_cablob) - sslc->primary.native_ca_store = TRUE; + ssl_proxy->native_ca_store = TRUE; #endif #ifdef CURL_CA_PATH if(!sslc->custom_capath && @@ -341,42 +359,55 @@ CURLcode Curl_ssl_easy_config_complete(struct Curl_easy *data, } #endif } - sslc->primary.CAfile = ssl_easy_steal(data, STRING_SSL_CAFILE_PROXY); - sslc->primary.CApath = ssl_easy_steal(data, STRING_SSL_CAPATH_PROXY); - sslc->primary.cipher_list = + + ssl_proxy->version = sslc->version; + ssl_proxy->version_max = sslc->version_max; + ssl_proxy->verifypeer = sslc->verifypeer; + ssl_proxy->verifyhost = sslc->verifyhost; + ssl_proxy->verifystatus = sslc->verifystatus; + ssl_proxy->cache_session = sslc->cache_session; + ssl_proxy->ssl_options = sslc->ssl_options; + ssl_proxy->CAfile = ssl_easy_steal(data, STRING_SSL_CAFILE_PROXY); + ssl_proxy->CApath = ssl_easy_steal(data, STRING_SSL_CAPATH_PROXY); + ssl_proxy->cipher_list = ssl_easy_steal(data, STRING_SSL_CIPHER_LIST_PROXY); - sslc->primary.cipher_list13 = + ssl_proxy->cipher_list13 = ssl_easy_steal(data, STRING_SSL_CIPHER13_LIST_PROXY); - sslc->primary.pinned_key = + ssl_proxy->pinned_key = ssl_easy_steal(data, STRING_SSL_PINNEDPUBLICKEY_PROXY); - sslc->primary.cert_blob = data->set.blobs[BLOB_CERT_PROXY]; - sslc->primary.ca_info_blob = data->set.blobs[BLOB_CAINFO_PROXY]; - sslc->primary.issuercert = ssl_easy_steal(data, STRING_SSL_ISSUERCERT_PROXY); - sslc->primary.issuercert_blob = data->set.blobs[BLOB_SSL_ISSUERCERT_PROXY]; - sslc->primary.CRLfile = ssl_easy_steal(data, STRING_SSL_CRLFILE_PROXY); - sslc->primary.cert_type = ssl_easy_steal(data, STRING_CERT_TYPE_PROXY); - sslc->primary.key = ssl_easy_steal(data, STRING_KEY_PROXY); - sslc->primary.key_type = ssl_easy_steal(data, STRING_KEY_TYPE_PROXY); - sslc->primary.key_passwd = ssl_easy_steal(data, STRING_KEY_PASSWD_PROXY); - sslc->primary.clientcert = ssl_easy_steal(data, STRING_CERT_PROXY); - sslc->primary.key_blob = data->set.blobs[BLOB_KEY_PROXY]; + ssl_proxy->cert_blob = data->set.blobs[BLOB_CERT_PROXY]; + ssl_proxy->ca_info_blob = data->set.blobs[BLOB_CAINFO_PROXY]; + ssl_proxy->issuercert = ssl_easy_steal(data, STRING_SSL_ISSUERCERT_PROXY); + ssl_proxy->issuercert_blob = data->set.blobs[BLOB_SSL_ISSUERCERT_PROXY]; + ssl_proxy->CRLfile = ssl_easy_steal(data, STRING_SSL_CRLFILE_PROXY); + ssl_proxy->cert_type = ssl_easy_steal(data, STRING_CERT_TYPE_PROXY); + ssl_proxy->key = ssl_easy_steal(data, STRING_KEY_PROXY); + ssl_proxy->key_type = ssl_easy_steal(data, STRING_KEY_TYPE_PROXY); + ssl_proxy->key_passwd = ssl_easy_steal(data, STRING_KEY_PASSWD_PROXY); + ssl_proxy->clientcert = ssl_easy_steal(data, STRING_CERT_PROXY); + ssl_proxy->key_blob = data->set.blobs[BLOB_KEY_PROXY]; +#else + (void)ssl_proxy; #endif /* CURL_DISABLE_PROXY */ return CURLE_OK; } -CURLcode Curl_ssl_conn_config_init(struct Curl_easy *data, - struct connectdata *conn) +CURLcode Curl_ssl_conn_config_clone(struct ssl_filter_config *ssl_config, + struct ssl_filter_config *proxy_ssl_config, + struct connectdata *conn) { /* Clone "primary" SSL configurations from the easy handle to * the connection. They are used for connection cache matching and * probably outlive the easy handle */ - if(!clone_ssl_primary_config(&data->set.ssl.primary, &conn->ssl_config)) + if(!clone_ssl_primary_config(ssl_config, &conn->ssl_config)) return CURLE_OUT_OF_MEMORY; #ifndef CURL_DISABLE_PROXY - if(!clone_ssl_primary_config(&data->set.proxy_ssl.primary, - &conn->proxy_ssl_config)) + if(proxy_ssl_config && + !clone_ssl_primary_config(proxy_ssl_config, &conn->proxy_ssl_config)) return CURLE_OUT_OF_MEMORY; +#else + (void)proxy_ssl_config; #endif return CURLE_OK; } @@ -393,13 +424,14 @@ void Curl_ssl_conn_config_update(struct Curl_easy *data, bool for_proxy) { /* May be called on an easy that has no connection yet */ if(data->conn) { - struct ssl_primary_config *src, *dest; + struct ssl_easy_config *src; + struct ssl_filter_config *dest; #ifndef CURL_DISABLE_PROXY - src = for_proxy ? &data->set.proxy_ssl.primary : &data->set.ssl.primary; + src = for_proxy ? &data->set.proxy_ssl : &data->set.ssl; dest = for_proxy ? &data->conn->proxy_ssl_config : &data->conn->ssl_config; #else (void)for_proxy; - src = &data->set.ssl.primary; + src = &data->set.ssl; dest = &data->conn->ssl_config; #endif dest->verifyhost = src->verifyhost; diff --git a/lib/vtls/vtls_config.h b/lib/vtls/vtls_config.h index 5872a13cdf0f..5de66530e616 100644 --- a/lib/vtls/vtls_config.h +++ b/lib/vtls/vtls_config.h @@ -29,7 +29,7 @@ struct Curl_easy; struct connectdata; struct Curl_peer; -struct ssl_primary_config { +struct ssl_filter_config { char *CApath; /* certificate directory (does not work on Windows) */ char *CAfile; /* certificate to verify peer against */ char *issuercert; /* optional issuer certificate filename */ @@ -54,43 +54,48 @@ struct ssl_primary_config { BIT(verifypeer); /* set TRUE if this is desired */ BIT(verifyhost); /* set TRUE if CN/SAN must match hostname */ BIT(verifystatus); /* set TRUE if certificate status must be checked */ - BIT(native_ca_store); /* use the native CA store of operating system */ + BIT(native_ca_store); /* use the native CA store of operating system */ BIT(cache_session); /* cache session or not */ BIT(deep_copy); /* members are deep copies, eg. owned here */ + BIT(earlydata); /* use TLS 1.3 early data */ BIT(auto_client_cert); /* automatically locate and use a client certificate for authentication (Schannel) */ -}; - -struct ssl_config_data { - struct ssl_primary_config primary; - long certverifyresult; /* result from the certificate verification */ - BIT(certinfo); /* gather lots of certificate info */ - BIT(earlydata); /* use TLS 1.3 early data */ BIT(enable_beast); /* allow this flaw for interoperability's sake */ - BIT(no_revoke); /* disable SSL certificate revocation checks */ BIT(no_partialchain); /* do not accept partial certificate chains */ + BIT(no_revoke); /* disable SSL certificate revocation checks */ BIT(revoke_best_effort); /* ignore SSL revocation offline/missing revocation list errors */ +}; + +struct ssl_easy_config { + long certverifyresult; /* result from the certificate verification */ + uint32_t version_max; /* max supported version the client wants to use */ + uint8_t version; /* what version the client wants to use */ + uint8_t ssl_options; /* the CURLOPT_SSL_OPTIONS bitmask */ + BIT(verifypeer); /* set TRUE if this is desired */ + BIT(verifyhost); /* set TRUE if CN/SAN must match hostname */ + BIT(verifystatus); /* set TRUE if certificate status must be checked */ + BIT(cache_session); /* cache session or not */ + BIT(certinfo); /* gather lots of certificate info */ BIT(custom_cafile); /* application has set custom CA file */ BIT(custom_capath); /* application has set custom CA path */ BIT(custom_cablob); /* application has set custom CA blob */ }; -void Curl_ssl_config_init(struct ssl_primary_config *sslc); -void Curl_ssl_config_cleanup(struct ssl_primary_config *sslc); +void Curl_ssl_config_init(struct ssl_easy_config *sslc); +void Curl_ssl_config_cleanup(struct ssl_filter_config *sslc); -/** - * Init the `data->set.ssl` and `data->set.proxy_ssl` for - * connection matching use. - */ -CURLcode Curl_ssl_easy_config_complete(struct Curl_easy *data, - struct Curl_peer *origin); +/* Init the SSL filter configs *shallow* for connection matching + * with origin and data's configuration settings. */ +CURLcode Curl_ssl_filter_config_tmp_init( + struct Curl_easy *data, struct Curl_peer *origin, + struct ssl_filter_config *ssl_origin, + struct ssl_filter_config *ssl_proxy); -/** - * Init SSL configs (main + proxy) for a new connection from the easy handle. - */ -CURLcode Curl_ssl_conn_config_init(struct Curl_easy *data, - struct connectdata *conn); +/* Clone the given filter configs into the connection. */ +CURLcode Curl_ssl_conn_config_clone(struct ssl_filter_config *ssl_config, + struct ssl_filter_config *proxy_ssl_config, + struct connectdata *conn); /** * Free allocated resources in SSL configs (main + proxy) for @@ -99,11 +104,12 @@ CURLcode Curl_ssl_conn_config_init(struct Curl_easy *data, void Curl_ssl_conn_config_cleanup(struct connectdata *conn); /** - * Return TRUE iff SSL configuration from `data` is functionally the + * Return TRUE if `conn_config` is functionally the * same as the one on `candidate`. * @param proxy match the proxy SSL config or the main one */ bool Curl_ssl_conn_config_match(struct Curl_easy *data, + struct ssl_filter_config *conn_config, struct connectdata *candidate, bool proxy); diff --git a/lib/vtls/vtls_scache.c b/lib/vtls/vtls_scache.c index c703302be849..d9564c11ca88 100644 --- a/lib/vtls/vtls_scache.c +++ b/lib/vtls/vtls_scache.c @@ -124,7 +124,7 @@ static CURLcode cf_ssl_peer_key_add_hash(struct dynbuf *buf, } static CURLcode cf_ssl_peer_key_add_mtls(struct dynbuf *buf, - struct ssl_primary_config *ssl, + struct ssl_filter_config *ssl, bool *is_local) { CURLcode result = CURLE_OK; @@ -194,7 +194,7 @@ static CURLcode ssl_peer_key_add_transport(struct dynbuf *buf, } static CURLcode ssl_peer_key_add_vrfy(struct dynbuf *buf, - struct ssl_primary_config *ssl, + struct ssl_filter_config *ssl, const struct ssl_peer *peer) { CURLcode result; @@ -225,7 +225,7 @@ static CURLcode ssl_peer_key_add_vrfy(struct dynbuf *buf, return CURLE_OK; } -static CURLcode ssl_peer_key_build(struct ssl_primary_config *ssl, +static CURLcode ssl_peer_key_build(struct ssl_filter_config *ssl, const struct ssl_peer *peer, const char *tls_id, char **ppeer_key) @@ -344,7 +344,7 @@ static CURLcode ssl_peer_key_build(struct ssl_primary_config *ssl, } CURLcode Curl_ssl_peer_key_make(const struct ssl_peer *peer, - struct ssl_primary_config *sslc, + struct ssl_filter_config *sslc, const char *tls_id, char **ppeer_key) { @@ -676,8 +676,9 @@ void Curl_ssl_scache_destroy(struct Curl_ssl_scache *scache) bool Curl_ssl_scache_use(struct Curl_cfilter *cf, struct Curl_easy *data) { if(cf_ssl_scache_get(data)) { - struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); - return ssl_config ? ssl_config->primary.cache_session : FALSE; + struct ssl_filter_config *conn_config = + Curl_ssl_cf_get_filter_config(cf); + return conn_config ? conn_config->cache_session : FALSE; } return FALSE; } @@ -740,7 +741,7 @@ bool Curl_ssl_scache_is_locked_by_current_thread(struct Curl_easy *data) } static bool cf_ssl_scache_match_auth(struct Curl_ssl_scache_peer *peer, - struct ssl_primary_config *conn_config) + struct ssl_filter_config *conn_config) { if(!conn_config) { if(peer->clientcert || peer->key_passwd) @@ -757,7 +758,7 @@ static bool cf_ssl_scache_match_auth(struct Curl_ssl_scache_peer *peer, static CURLcode cf_ssl_find_peer_by_key(struct Curl_easy *data, struct Curl_ssl_scache *scache, const char *ssl_peer_key, - struct ssl_primary_config *conn_config, + struct ssl_filter_config *conn_config, struct Curl_ssl_scache_peer **ppeer) { size_t i, peer_key_len = 0; @@ -851,7 +852,7 @@ static struct Curl_ssl_scache_peer *cf_ssl_get_free_peer( static CURLcode cf_ssl_add_peer(struct Curl_easy *data, struct Curl_ssl_scache *scache, const char *ssl_peer_key, - struct ssl_primary_config *conn_config, + struct ssl_filter_config *conn_config, struct Curl_ssl_scache_peer **ppeer) { struct Curl_ssl_scache_peer *peer = NULL; @@ -924,7 +925,7 @@ static CURLcode cf_scache_add_session(struct Curl_cfilter *cf, struct Curl_ssl_session *s) { struct Curl_ssl_scache_peer *peer = NULL; - struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); + struct ssl_filter_config *conn_config = Curl_ssl_cf_get_filter_config(cf); CURLcode result = CURLE_OUT_OF_MEMORY; curl_off_t now = (curl_off_t)time(NULL); curl_off_t max_lifetime; @@ -979,11 +980,11 @@ CURLcode Curl_ssl_scache_put(struct Curl_cfilter *cf, struct Curl_ssl_session *s) { struct Curl_ssl_scache *scache = cf_ssl_scache_get(data); - struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); + struct ssl_filter_config *conn_config = Curl_ssl_cf_get_filter_config(cf); CURLcode result; - DEBUGASSERT(ssl_config); + DEBUGASSERT(conn_config); - if(!scache || !ssl_config->primary.cache_session) { + if(!scache || !conn_config->cache_session) { Curl_ssl_session_destroy(s); return CURLE_OK; } @@ -1013,7 +1014,7 @@ CURLcode Curl_ssl_scache_take(struct Curl_cfilter *cf, struct Curl_ssl_session **ps) { struct Curl_ssl_scache *scache = cf_ssl_scache_get(data); - struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); + struct ssl_filter_config *conn_config = Curl_ssl_cf_get_filter_config(cf); struct Curl_ssl_scache_peer *peer = NULL; struct Curl_llist_node *n; struct Curl_ssl_session *s = NULL; @@ -1057,7 +1058,7 @@ CURLcode Curl_ssl_scache_add_obj(struct Curl_cfilter *cf, Curl_ssl_scache_obj_dtor *sobj_dtor_cb) { struct Curl_ssl_scache *scache = cf_ssl_scache_get(data); - struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); + struct ssl_filter_config *conn_config = Curl_ssl_cf_get_filter_config(cf); struct Curl_ssl_scache_peer *peer = NULL; CURLcode result; @@ -1089,7 +1090,7 @@ void *Curl_ssl_scache_get_obj(struct Curl_cfilter *cf, const char *ssl_peer_key) { struct Curl_ssl_scache *scache = cf_ssl_scache_get(data); - struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); + struct ssl_filter_config *conn_config = Curl_ssl_cf_get_filter_config(cf); struct Curl_ssl_scache_peer *peer = NULL; CURLcode result; void *sobj; @@ -1114,7 +1115,7 @@ void Curl_ssl_scache_remove_all(struct Curl_cfilter *cf, const char *ssl_peer_key) { struct Curl_ssl_scache *scache = cf_ssl_scache_get(data); - struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); + struct ssl_filter_config *conn_config = Curl_ssl_cf_get_filter_config(cf); struct Curl_ssl_scache_peer *peer = NULL; CURLcode result; diff --git a/lib/vtls/vtls_scache.h b/lib/vtls/vtls_scache.h index c9f35e800f62..9a393c72f6fd 100644 --- a/lib/vtls/vtls_scache.h +++ b/lib/vtls/vtls_scache.h @@ -62,7 +62,7 @@ void Curl_ssl_scache_destroy(struct Curl_ssl_scache *scache); * @param ppeer_key on successful return, the key generated */ CURLcode Curl_ssl_peer_key_make(const struct ssl_peer *peer, - struct ssl_primary_config *sslc, + struct ssl_filter_config *sslc, const char *tls_id, char **ppeer_key); diff --git a/lib/vtls/wolfssl.c b/lib/vtls/wolfssl.c index 12e8f9702df7..facac6f34c63 100644 --- a/lib/vtls/wolfssl.c +++ b/lib/vtls/wolfssl.c @@ -525,7 +525,7 @@ static bool wssl_apply_session( Curl_wssl_init_session_reuse_cb *sess_reuse_cb, struct Curl_ssl_session *scs) { - struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); + struct ssl_filter_config *conn_config = Curl_ssl_cf_get_filter_config(cf); CURLcode result = CURLE_OK; WOLFSSL_SESSION *session = NULL; bool success = FALSE; @@ -546,7 +546,7 @@ static bool wssl_apply_session( infof(data, "SSL reusing session with ALPN '%s'", scs->alpn ? scs->alpn : "-"); success = TRUE; - if(ssl_config->earlydata && + if(conn_config->earlydata && !cf->conn->bits.connect_only && !strcmp("TLSv1.3", wolfSSL_get_version(wss->ssl))) { bool do_early_data = FALSE; @@ -586,7 +586,7 @@ static CURLcode wssl_populate_x509_store(struct Curl_cfilter *cf, WOLFSSL_X509_STORE *store, struct wssl_ctx *wssl) { - struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); + struct ssl_filter_config *conn_config = Curl_ssl_cf_get_filter_config(cf); const struct curl_blob *ca_info_blob = conn_config->ca_info_blob; const char * const ssl_cafile = /* CURLOPT_CAINFO_BLOB overrides CURLOPT_CAINFO */ @@ -709,7 +709,7 @@ static bool wssl_cached_x509_store_expired(struct Curl_easy *data, static bool wssl_cached_x509_store_different(struct Curl_cfilter *cf, const struct wssl_x509_share *mb) { - struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); + struct ssl_filter_config *conn_config = Curl_ssl_cf_get_filter_config(cf); if(!mb->CAfile || !conn_config->CAfile) return mb->CAfile != conn_config->CAfile; @@ -740,7 +740,7 @@ static void wssl_set_cached_x509_store(struct Curl_cfilter *cf, struct Curl_easy *data, WOLFSSL_X509_STORE *store) { - struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); + struct ssl_filter_config *conn_config = Curl_ssl_cf_get_filter_config(cf); struct Curl_multi *multi = data->multi; struct wssl_x509_share *share; @@ -790,8 +790,7 @@ CURLcode Curl_wssl_setup_x509_store(struct Curl_cfilter *cf, struct Curl_easy *data, struct wssl_ctx *wssl) { - struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); - struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); + struct ssl_filter_config *conn_config = Curl_ssl_cf_get_filter_config(cf); CURLcode result = CURLE_OK; WOLFSSL_X509_STORE *cached_store; bool cache_criteria_met; @@ -806,7 +805,7 @@ CURLcode Curl_wssl_setup_x509_store(struct Curl_cfilter *cf, conn_config->verifypeer && !conn_config->CApath && !conn_config->ca_info_blob && - !ssl_config->primary.CRLfile && + !conn_config->CRLfile && !conn_config->native_ca_store; cached_store = cache_criteria_met ? wssl_get_cached_x509_store(cf, data) @@ -908,17 +907,17 @@ static int wssl_legacy_CTX_set_max_proto_version(WOLFSSL_CTX *ctx, int version) #endif /* OPENSSL_EXTRA */ static CURLcode wssl_client_cert(struct Curl_easy *data, - struct ssl_config_data *ssl_config, + struct ssl_filter_config *conn_config, struct wssl_ctx *wctx) { /* Load the client certificate, and private key */ #ifndef NO_FILESYSTEM - if(ssl_config->primary.cert_blob || ssl_config->primary.clientcert) { - const char *cert_file = ssl_config->primary.clientcert; - const char *key_file = ssl_config->primary.key; - const struct curl_blob *cert_blob = ssl_config->primary.cert_blob; - const struct curl_blob *key_blob = ssl_config->primary.key_blob; - int file_type = wssl_do_file_type(ssl_config->primary.cert_type); + if(conn_config->cert_blob || conn_config->clientcert) { + const char *cert_file = conn_config->clientcert; + const char *key_file = conn_config->key; + const struct curl_blob *cert_blob = conn_config->cert_blob; + const struct curl_blob *key_blob = conn_config->key_blob; + int file_type = wssl_do_file_type(conn_config->cert_type); int rc; switch(file_type) { @@ -949,7 +948,7 @@ static CURLcode wssl_client_cert(struct Curl_easy *data, key_file = cert_file; } else - file_type = wssl_do_file_type(ssl_config->primary.key_type); + file_type = wssl_do_file_type(conn_config->key_type); rc = key_blob ? wolfSSL_CTX_use_PrivateKey_buffer(wctx->ssl_ctx, key_blob->data, @@ -961,10 +960,10 @@ static CURLcode wssl_client_cert(struct Curl_easy *data, } } #else /* NO_FILESYSTEM */ - if(ssl_config->primary.cert_blob) { - const struct curl_blob *cert_blob = ssl_config->primary.cert_blob; - const struct curl_blob *key_blob = ssl_config->primary.key_blob; - int file_type = wssl_do_file_type(ssl_config->primary.cert_type); + if(conn_config->cert_blob) { + const struct curl_blob *cert_blob = conn_config->cert_blob; + const struct curl_blob *key_blob = conn_config->key_blob; + int file_type = wssl_do_file_type(conn_config->cert_type); int rc; switch(file_type) { @@ -989,7 +988,7 @@ static CURLcode wssl_client_cert(struct Curl_easy *data, if(!key_blob) key_blob = cert_blob; else - file_type = wssl_do_file_type(ssl_config->primary.key_type); + file_type = wssl_do_file_type(conn_config->key_type); if(wolfSSL_CTX_use_PrivateKey_buffer(wctx->ssl_ctx, key_blob->data, (long)key_blob->len, @@ -1003,7 +1002,7 @@ static CURLcode wssl_client_cert(struct Curl_easy *data, } static CURLcode ssl_version(struct Curl_easy *data, - struct ssl_primary_config *conn_config, + struct ssl_filter_config *conn_config, struct wssl_ctx *wctx, int *min_version, int *max_version) { @@ -1077,7 +1076,7 @@ static CURLcode ssl_version(struct Curl_easy *data, static CURLcode wssl_init_ciphers(struct Curl_easy *data, struct wssl_ctx *wctx, - struct ssl_primary_config *conn_config, + struct ssl_filter_config *conn_config, int tls_min, int tls_max) { #ifndef WOLFSSL_TLS13 @@ -1143,7 +1142,7 @@ static CURLcode wssl_init_ciphers(struct Curl_easy *data, static CURLcode wssl_init_curves(struct Curl_easy *data, struct wssl_ctx *wctx, - struct ssl_primary_config *conn_config) + struct ssl_filter_config *conn_config) { char *curves = conn_config->curves; /* Without an explicit list, leave the key share group selection to @@ -1330,8 +1329,7 @@ CURLcode Curl_wssl_ctx_init(struct wssl_ctx *wctx, void *ssl_user_data, Curl_wssl_init_session_reuse_cb *sess_reuse_cb) { - struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); - struct ssl_primary_config *conn_config; + struct ssl_filter_config *conn_config; WOLFSSL_METHOD *req_method = NULL; struct alpn_spec alpns; CURLcode result = CURLE_FAILED_INIT; @@ -1340,7 +1338,7 @@ CURLcode Curl_wssl_ctx_init(struct wssl_ctx *wctx, DEBUGASSERT(!wctx->ssl_ctx); DEBUGASSERT(!wctx->ssl); - conn_config = Curl_ssl_cf_get_primary_config(cf); + conn_config = Curl_ssl_cf_get_filter_config(cf); if(!conn_config) { result = CURLE_FAILED_INIT; goto out; @@ -1378,7 +1376,7 @@ CURLcode Curl_wssl_ctx_init(struct wssl_ctx *wctx, if(result) goto out; - result = wssl_client_cert(data, ssl_config, wctx); + result = wssl_client_cert(data, conn_config, wctx); if(result) goto out; @@ -1493,7 +1491,7 @@ static CURLcode wssl_connect_step1(struct Curl_cfilter *cf, { struct ssl_connect_data *connssl = cf->ctx; struct wssl_ctx *wssl = (struct wssl_ctx *)connssl->backend; - struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); + struct ssl_filter_config *conn_config = Curl_ssl_cf_get_filter_config(cf); CURLcode result; DEBUGASSERT(wssl); @@ -1680,7 +1678,7 @@ static CURLcode wssl_handshake(struct Curl_cfilter *cf, struct Curl_easy *data) { struct ssl_connect_data *connssl = cf->ctx; struct wssl_ctx *wssl = (struct wssl_ctx *)connssl->backend; - struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); + struct ssl_filter_config *conn_config = Curl_ssl_cf_get_filter_config(cf); int ret = -1, detail; CURLcode result; diff --git a/tests/unit/unit3303.c b/tests/unit/unit3303.c index 56f52ebf5730..a5ef56895f9d 100644 --- a/tests/unit/unit3303.c +++ b/tests/unit/unit3303.c @@ -34,14 +34,16 @@ static CURLcode test_unit3303(const char *arg) #ifdef USE_SSL CURL *curl; + struct Curl_easy *data = NULL; struct connectdata *conn; - struct ssl_primary_config *primary; char *saved; static char alt_passwd[] = "wrong"; static char alt_key[] = "other.key"; static char alt_ktype[] = "DER"; static char alt_ctype[] = "P12"; struct Curl_peer *origin = NULL; + struct ssl_filter_config ssl_config; + struct ssl_filter_config proxy_ssl_config; CURLcode result; curl_global_init(CURL_GLOBAL_ALL); @@ -50,16 +52,16 @@ static CURLcode test_unit3303(const char *arg) curl_global_cleanup(); goto unit_test_abort; } + data = (struct Curl_easy *)curl; - result = Curl_peer_create((struct Curl_easy *)curl, - &Curl_scheme_https, + result = Curl_peer_create(data, &Curl_scheme_https, "test.curl.se", 1234, &origin); if(result) { curl_easy_cleanup(curl); curl_global_cleanup(); goto unit_test_abort; } - Curl_peer_link(&((struct Curl_easy *)curl)->state.initial_origin, origin); + Curl_peer_link(&data->state.initial_origin, origin); curl_easy_setopt(curl, CURLOPT_SSLCERT, "client.pem"); curl_easy_setopt(curl, CURLOPT_SSLKEY, "client.key"); @@ -67,7 +69,10 @@ static CURLcode test_unit3303(const char *arg) curl_easy_setopt(curl, CURLOPT_SSLCERTTYPE, "PEM"); curl_easy_setopt(curl, CURLOPT_SSLKEYTYPE, "PEM"); - if(Curl_ssl_easy_config_complete((struct Curl_easy *)curl, origin)) { + memset(&ssl_config, 0, sizeof(ssl_config)); + memset(&proxy_ssl_config, 0, sizeof(proxy_ssl_config)); + if(Curl_ssl_filter_config_tmp_init(data, origin, &ssl_config, + &proxy_ssl_config)) { Curl_peer_unlink(&origin); curl_easy_cleanup(curl); curl_global_cleanup(); @@ -75,7 +80,8 @@ static CURLcode test_unit3303(const char *arg) } conn = curlx_calloc(1, sizeof(*conn)); - if(!conn || Curl_ssl_conn_config_init((struct Curl_easy *)curl, conn)) { + if(!conn || + Curl_ssl_conn_config_clone(&ssl_config, NULL, conn)) { if(conn) Curl_ssl_conn_config_cleanup(conn); curlx_free(conn); @@ -86,49 +92,42 @@ static CURLcode test_unit3303(const char *arg) } /* Baseline: identical config must match. */ - fail_unless(Curl_ssl_conn_config_match((struct Curl_easy *)curl, conn, - FALSE), + fail_unless(Curl_ssl_conn_config_match(data, &ssl_config, conn, FALSE), "identical mTLS config should match"); - primary = &((struct Curl_easy *)curl)->set.ssl.primary; - /* Different key_passwd must not match. */ - saved = primary->key_passwd; - primary->key_passwd = alt_passwd; - fail_unless(!Curl_ssl_conn_config_match((struct Curl_easy *)curl, conn, - FALSE), + saved = ssl_config.key_passwd; + ssl_config.key_passwd = alt_passwd; + fail_unless(!Curl_ssl_conn_config_match(data, &ssl_config, conn, FALSE), "different key_passwd must not reuse conn"); - primary->key_passwd = saved; + ssl_config.key_passwd = saved; /* Different key path must not match. */ - saved = primary->key; - primary->key = alt_key; - fail_unless(!Curl_ssl_conn_config_match((struct Curl_easy *)curl, conn, - FALSE), + saved = ssl_config.key; + ssl_config.key = alt_key; + fail_unless(!Curl_ssl_conn_config_match(data, &ssl_config, conn, FALSE), "different key must not reuse conn"); - primary->key = saved; + ssl_config.key = saved; /* Different key type must not match. */ - saved = primary->key_type; - primary->key_type = alt_ktype; - fail_unless(!Curl_ssl_conn_config_match((struct Curl_easy *)curl, conn, - FALSE), + saved = ssl_config.key_type; + ssl_config.key_type = alt_ktype; + fail_unless(!Curl_ssl_conn_config_match(data, &ssl_config, conn, FALSE), "different key_type must not reuse conn"); - primary->key_type = saved; + ssl_config.key_type = saved; /* Different cert type must not match. */ - saved = primary->cert_type; - primary->cert_type = alt_ctype; - fail_unless(!Curl_ssl_conn_config_match((struct Curl_easy *)curl, conn, - FALSE), + saved = ssl_config.cert_type; + ssl_config.cert_type = alt_ctype; + fail_unless(!Curl_ssl_conn_config_match(data, &ssl_config, conn, FALSE), "different cert_type must not reuse conn"); - primary->cert_type = saved; + ssl_config.cert_type = saved; /* All fields restored: must match again. */ - fail_unless(Curl_ssl_conn_config_match((struct Curl_easy *)curl, conn, - FALSE), + fail_unless(Curl_ssl_conn_config_match(data, &ssl_config, conn, FALSE), "restored mTLS config should match"); + Curl_ssl_config_cleanup(&ssl_config); Curl_ssl_conn_config_cleanup(conn); curlx_free(conn); curl_easy_cleanup(curl); diff --git a/tests/unit/unit3304.c b/tests/unit/unit3304.c index 7336aa731bda..1d5546a13ee9 100644 --- a/tests/unit/unit3304.c +++ b/tests/unit/unit3304.c @@ -45,7 +45,7 @@ static CURLcode test_unit3304(const char *arg) #ifdef USE_SSL struct Curl_peer origin; struct ssl_peer peer; - struct ssl_primary_config ssl; + struct ssl_filter_config ssl; char *key1 = NULL; char *key2 = NULL; static char base_hostname[] = "example.com"; From cbd19a9b267ae4c38037bde30644e90f92dae103 Mon Sep 17 00:00:00 2001 From: Viktor Szakats Date: Fri, 11 Sep 2026 00:08:59 +0200 Subject: [PATCH 105/481] GHA: drop local fuzzer job in favor of running within the curl-fuzzer repo To save resources by fuzzing on a scheduled basis within the curl/curl-fuzzer repo, replacing runs on every curl/curl PR commit push and master pushes. Also to keep the CI build cycle under the 10-minute mark. As the scope of fuzzing extends, the necessary resources are growing with it. At the time of writing this, these consist of: - 1 build job. (building curl, curl dependencies, and fuzzer components.) - taking 15 minutes (with optimizations and build caching). Actual fuzzing starts after completing this step. - 39 fuzzing jobs. - taking 2-4 minutes (average ~3.5 minutes) per job. They are parallelized by GHA, taking 4-5 minutes in total. - build + fuzzing completing in 18-20 minutes. - 700 MB worth of fuzzer artifacts per run. - 31 Action build cache entries, 74 MB each, 2.2 GB in total. (that's with trimming the build directory before caching it) Compare this to the rest of jobs where the longest running one finishes under 10 minutes. (The local fuzzer job was also using an unpinned Action (and another unpinned one transitively), which is in practice unfixable, and which caused exceptions in systems ensuring that we pin everything. The scope of this issue reduces to the fuzzer repo after this patch.) Ref: https://github.com/curl/curl/pull/22904#issuecomment-5625358492 Ref: #22907 Closes #22908 --- .github/workflows/fuzz.yml | 49 -------------------------------------- docs/tests/CI.md | 5 ++-- 2 files changed, 2 insertions(+), 52 deletions(-) delete mode 100644 .github/workflows/fuzz.yml diff --git a/.github/workflows/fuzz.yml b/.github/workflows/fuzz.yml deleted file mode 100644 index 374f597adbea..000000000000 --- a/.github/workflows/fuzz.yml +++ /dev/null @@ -1,49 +0,0 @@ -# Copyright (C) Daniel Stenberg, , et al. -# -# SPDX-License-Identifier: curl - -name: 'Fuzzer' - -'on': - push: - branches: - - master - - 'curl-*' - - '*/ci' - paths-ignore: - - '**/*.md' - - '**/CMakeLists.txt' - - '.circleci/**' - - 'appveyor.*' - - 'CMake/**' - - 'Dockerfile' - - 'projects/**' - - 'tests/data/**' - pull_request: - branches: - - master - - 'curl-*' - paths-ignore: - - '**/*.md' - - '**/CMakeLists.txt' - - '.circleci/**' - - 'appveyor.*' - - 'CMake/**' - - 'Dockerfile' - - 'projects/**' - - 'tests/data/**' - -concurrency: - # Hard-coded workflow name to avoid colliding with curl-fuzzer's group - group: curl-fuzz-${{ github.event.pull_request.number || github.sha }} - cancel-in-progress: true - -permissions: {} -cache-mode: write - -env: - DO_NOT_TRACK: '1' - -jobs: - Fuzzing: - uses: curl/curl-fuzzer/.github/workflows/ci.yml@master # zizmor: ignore[unpinned-uses] diff --git a/docs/tests/CI.md b/docs/tests/CI.md index e68c9bc88509..4fe887c0d137 100644 --- a/docs/tests/CI.md +++ b/docs/tests/CI.md @@ -29,7 +29,6 @@ Consider the following table while looking at pull request failures: | CI platform as shown in PR | State | What to look at next | | ----------------------------------- | ------ | -------------------------- | | Linux / macOS / Windows / ... | stable | all errors and failures | -| Fuzzer | stable | fuzzing results | | Code analyzers | stable | new findings | | checkdocs / checksrc / dist / ... | stable | all errors and failures | | AppVeyor | stable | all errors and failures | @@ -62,13 +61,13 @@ are configured: GitHub Actions runs the following tests: - Tests with a variety of different compilation options, OSes, CPUs. -- Fuzz tests ([see the curl-fuzzer repo for more - info](https://github.com/curl/curl-fuzzer)). - Static analysis and sanitizers: clang-tidy, address sanitizer, memory sanitizer, thread sanitizer, CodeQL, valgrind, torture tests. These are each configured in different files in `.github/workflows`. +Fuzz tests run in the [curl-fuzzer repo](https://github.com/curl/curl-fuzzer). + ### AppVeyor CI AppVeyor runs a variety of different Windows builds, with different compilation From 805b9418a9287e3a477c3bd0ba322ad22158656c Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 10 Sep 2026 21:24:37 +0000 Subject: [PATCH 106/481] GHA: update pip dependencies - pyrefly to v1.3.0 - ruff to v0.16.7 Closes #22891 Closes #22909 --- .github/scripts/requirements.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/scripts/requirements.txt b/.github/scripts/requirements.txt index e51ba9850e17..75b32d076e59 100644 --- a/.github/scripts/requirements.txt +++ b/.github/scripts/requirements.txt @@ -4,6 +4,6 @@ cmakelang==0.6.13 codespell==2.4.3 -pyrefly==1.2.0 +pyrefly==1.3.0 reuse==6.2.0 -ruff==0.16.5 +ruff==0.16.7 From 3ddbd522b9fb2cc45f228d38a26bb0458eb86d35 Mon Sep 17 00:00:00 2001 From: Stefan Eissing Date: Thu, 10 Sep 2026 13:09:21 +0200 Subject: [PATCH 107/481] dnscache/conncache tweaks - pass more `struct curltime` pointers around - as hash keys are now bytes, make the dnscache entry key binary as well, no longer using printf() for formatting - restrict hostname entry lengths to UINT16_MAX and fail otherwise - add a `permanent` bit to entries Closes #22900 --- lib/conncache.c | 5 +- lib/conncache.h | 3 +- lib/multi.c | 5 +- lib/vdns/dnscache.c | 118 ++++++++++++++++++++++++------------------ lib/vdns/dnscache.h | 14 +++-- tests/unit/unit1607.c | 42 ++++++++++----- 6 files changed, 110 insertions(+), 77 deletions(-) diff --git a/lib/conncache.c b/lib/conncache.c index 845dedf431a4..eb0a7638cd45 100644 --- a/lib/conncache.c +++ b/lib/conncache.c @@ -646,11 +646,11 @@ static bool cpool_foreach(struct Curl_easy *data, * Return TRUE if idle connection kept in pool, FALSE if closed. */ bool Curl_cpool_conn_now_idle(struct Curl_easy *data, - struct connectdata *conn) + struct connectdata *conn, + const struct curltime *pnow) { struct cpool *cpool = cpool_get_instance(data); struct connectdata *oldest_idle = NULL; - const struct curltime *pnow = NULL; struct Curl_easy *admin; unsigned int maxconnects; bool kept = TRUE; @@ -672,7 +672,6 @@ bool Curl_cpool_conn_now_idle(struct Curl_easy *data, } /* remember times, connection had been used just before */ - pnow = Curl_pgrs_now(data); conn->lastchecked_ms = conn->lastupkeep_ms = conn->lastused_ms = curlx_ptimediff_ms(pnow, &conn->created); if(cpool && maxconnects) { diff --git a/lib/conncache.h b/lib/conncache.h index a1d7973576b3..975d940b2fa9 100644 --- a/lib/conncache.h +++ b/lib/conncache.h @@ -137,7 +137,8 @@ bool Curl_cpool_find(struct Curl_easy *data, * Return TRUE if idle connection kept in pool, FALSE if closed. */ bool Curl_cpool_conn_now_idle(struct Curl_easy *data, - struct connectdata *conn); + struct connectdata *conn, + const struct curltime *pnow); /** * Scans the connection pool for half-open/dead diff --git a/lib/multi.c b/lib/multi.c index a6ee80edb738..580b9c7c8f10 100644 --- a/lib/multi.c +++ b/lib/multi.c @@ -629,6 +629,7 @@ static void multi_done_locked(struct connectdata *conn, void *userdata) { struct multi_done_ctx *mdctx = userdata; + const struct curltime *pnow = Curl_pgrs_now(data); Curl_detach_connection(data); @@ -642,7 +643,7 @@ static void multi_done_locked(struct connectdata *conn, data->state.done = TRUE; /* called now! */ - Curl_dnscache_prune(data); + Curl_dnscache_prune(data, pnow); if(multi_conn_should_close(conn, data, (bool)mdctx->premature)) { CURL_TRC_M(data, "multi_done, terminating conn #%" FMT_OFF_T " to %s:%u, " @@ -663,7 +664,7 @@ static void multi_done_locked(struct connectdata *conn, } else { /* the connection is no longer in use by any transfer */ - if(Curl_cpool_conn_now_idle(data, conn)) { + if(Curl_cpool_conn_now_idle(data, conn, pnow)) { /* connection kept in the cpool */ infof(data, "Connection #%" FMT_OFF_T " to host %s:%u left intact", conn->connection_id, conn->origin->user_hostname, diff --git a/lib/vdns/dnscache.c b/lib/vdns/dnscache.c index 8993fabda085..1c89086aafce 100644 --- a/lib/vdns/dnscache.c +++ b/lib/vdns/dnscache.c @@ -56,7 +56,7 @@ #include "curlx/strcopy.h" #include "curlx/strparse.h" -#define MAX_HOSTCACHE_LEN (255 + 7) /* max FQDN + colon + port number + zero */ +#define MAX_HOSTCACHE_LEN (255 + 3) /* max FQDN + type + port */ #define MAX_DNS_CACHE_SIZE 29999 @@ -83,7 +83,7 @@ static void dnsc_str2id(struct dnsc_id *pid, char type, } struct dnsc_key { - char data[MAX_HOSTCACHE_LEN]; + uint8_t data[MAX_HOSTCACHE_LEN]; size_t len; }; @@ -94,14 +94,14 @@ struct dnsc_key { static void dnsc_id2key(struct dnsc_key *key, struct dnsc_id *id) { size_t namelen = curlx_strlen(&id->name); - if(namelen > (sizeof(key->data) - 8)) - namelen = sizeof(key->data) - 8; + if(namelen > (sizeof(key->data) - 3)) + namelen = sizeof(key->data) - 3; /* store and lower case the name */ key->data[0] = id->type; - Curl_strntolower(key->data + 1, curlx_str(&id->name), namelen); - /* include the terminating 0 in key length */ - key->len = namelen + 2 + - curl_msnprintf(&key->data[namelen + 1], 7, ":%u", id->port); + key->data[1] = (uint8_t)((id->port >> 8) & 0xff); + key->data[2] = (uint8_t)(id->port & 0xff); + Curl_strntolower((char *)key->data + 3, curlx_str(&id->name), namelen); + key->len = namelen + 3; } static void dnscache_entry_free(struct Curl_dns_entry *dns) @@ -114,7 +114,7 @@ static void dnscache_entry_free(struct Curl_dns_entry *dns) } struct dnscache_prune_data { - struct curltime now; + const struct curltime *pnow; timediff_t oldest_ms; /* oldest time in cache not pruned. */ timediff_t max_age_ms; }; @@ -131,15 +131,27 @@ static int dnscache_entry_is_stale(void *datap, void *hc) struct dnscache_prune_data *prune = (struct dnscache_prune_data *)datap; struct Curl_dns_entry *dns = (struct Curl_dns_entry *)hc; - if(dns->timestamp.tv_sec || dns->timestamp.tv_usec) { + if(!dns->permanent) { /* get age in milliseconds */ - timediff_t age = curlx_ptimediff_ms(&prune->now, &dns->timestamp); - if(!dns->addr) - age *= 2; /* negative entries age twice as fast */ - if(age >= prune->max_age_ms) + timediff_t age_ms = curlx_ptimediff_ms(prune->pnow, &dns->added); + switch(dns->type) { + case CURL_DNST_ADDR: + if(!dns->addr) + age_ms *= 2; /* negative entries age twice as fast */ + break; +#ifdef USE_HTTPSRR + case CURL_DNST_HTTPS: + if(!dns->hinfo) + age_ms *= 2; /* negative entries age twice as fast */ + break; +#endif + default: + break; + } + if(age_ms >= prune->max_age_ms) return TRUE; - if(age > prune->oldest_ms) - prune->oldest_ms = age; + if(age_ms > prune->oldest_ms) + prune->oldest_ms = age_ms; } return FALSE; } @@ -150,12 +162,12 @@ static int dnscache_entry_is_stale(void *datap, void *hc) */ static timediff_t dnscache_prune(struct Curl_hash *hostcache, timediff_t cache_timeout_ms, - struct curltime now) + const struct curltime *pnow) { struct dnscache_prune_data user; user.max_age_ms = cache_timeout_ms; - user.now = now; + user.pnow = pnow; user.oldest_ms = 0; Curl_hash_clean_with_criterium(hostcache, @@ -192,7 +204,7 @@ static void dnscache_unlock(struct Curl_easy *data, * Library-wide function for pruning the DNS cache. This function takes and * returns the appropriate locks. */ -void Curl_dnscache_prune(struct Curl_easy *data) +void Curl_dnscache_prune(struct Curl_easy *data, const struct curltime *pnow) { struct Curl_dnscache *dnscache = dnscache_get(data); /* the timeout may be set -1 (forever) */ @@ -207,7 +219,7 @@ void Curl_dnscache_prune(struct Curl_easy *data) do { /* Remove outdated and unused entries from the hostcache */ timediff_t oldest_ms = - dnscache_prune(&dnscache->entries, timeout_ms, *Curl_pgrs_now(data)); + dnscache_prune(&dnscache->entries, timeout_ms, pnow); if(Curl_hash_count(&dnscache->entries) > MAX_DNS_CACHE_SIZE) /* prune the ones over half this age */ @@ -233,11 +245,11 @@ void Curl_dnscache_clear(struct Curl_easy *data) } /* lookup address, returns entry if found and not stale */ -static CURLcode fetch_addr(struct Curl_easy *data, - struct Curl_dnscache *dnscache, - uint8_t dns_queries, - struct Curl_peer *peer, - struct Curl_dns_entry **pdns) +static CURLcode fetch_entry(struct Curl_easy *data, + struct Curl_dnscache *dnscache, + uint8_t dns_queries, + struct Curl_peer *peer, + struct Curl_dns_entry **pdns) { struct Curl_dns_entry *dns = NULL; struct dnsc_id id; @@ -272,7 +284,7 @@ static CURLcode fetch_addr(struct Curl_easy *data, /* See whether the returned entry is stale. Done before we release lock */ struct dnscache_prune_data user; - user.now = *Curl_pgrs_now(data); + user.pnow = Curl_pgrs_now(data); user.max_age_ms = data->set.dns_cache_timeout_ms; user.oldest_ms = 0; @@ -331,7 +343,7 @@ CURLcode Curl_dnscache_get(struct Curl_easy *data, CURLcode result = CURLE_OK; dnscache_lock(data, dnscache); - result = fetch_addr(data, dnscache, dns_queries, peer, &dns); + result = fetch_entry(data, dnscache, dns_queries, peer, &dns); if(!result && dns) dns->refcount++; /* we pass out a reference */ else if(result) { @@ -438,12 +450,14 @@ static bool dnscache_ai_has_family(struct Curl_addrinfo *ai, int ai_family) return FALSE; } -static struct Curl_dns_entry *dnsc_entry_create(struct Curl_easy *data, - struct dnsc_id *pid, +static struct Curl_dns_entry *dnsc_entry_create(struct dnsc_id *pid, bool permanent) { struct Curl_dns_entry *dns = NULL; + if(curlx_strlen(&pid->name) > UINT16_MAX) + goto out; + /* Create a new cache entry, struct already has the hostname NUL */ dns = curlx_calloc(1, sizeof(struct Curl_dns_entry) + curlx_strlen(&pid->name)); @@ -451,18 +465,11 @@ static struct Curl_dns_entry *dnsc_entry_create(struct Curl_easy *data, goto out; dns->refcount = 1; /* the cache has the first reference */ - dns->hostlen = curlx_strlen(&pid->name); dns->port = pid->port; + dns->hostlen = (uint16_t)curlx_strlen(&pid->name); if(dns->hostlen) memcpy(dns->hostname, curlx_str(&pid->name), dns->hostlen); - - if(permanent) { - dns->timestamp.tv_sec = 0; /* an entry that never goes stale */ - dns->timestamp.tv_usec = 0; /* an entry that never goes stale */ - } - else { - dns->timestamp = *Curl_pgrs_now(data); - } + dns->permanent = permanent; out: return dns; @@ -545,7 +552,7 @@ struct Curl_dns_entry *Curl_dnsc_mk_addr(struct Curl_easy *data, struct Curl_dns_entry *dns; dnsc_peer2id(&id, CURL_DNST_ADDR, peer); - dns = dnsc_entry_create(data, &id, FALSE); + dns = dnsc_entry_create(&id, FALSE); dns = dnsc_entry_assign_addr(data, dns, dns_queries, paddr, NULL); return dns; } @@ -560,11 +567,20 @@ struct Curl_dns_entry *Curl_dnsc_mk_addr2(struct Curl_easy *data, struct Curl_dns_entry *dns; dnsc_peer2id(&id, CURL_DNST_ADDR, peer); - dns = dnsc_entry_create(data, &id, FALSE); + dns = dnsc_entry_create(&id, FALSE); dns = dnsc_entry_assign_addr(data, dns, dns_queries, paddr1, paddr2); return dns; } +static struct Curl_dns_entry *dnsc_add_entry(struct Curl_easy *data, + struct Curl_dnscache *dnscache, + struct dnsc_key *key, + struct Curl_dns_entry *entry) +{ + entry->added = *Curl_pgrs_now(data); + return Curl_hash_add(&dnscache->entries, key->data, key->len, entry); +} + #ifdef USE_HTTPSRR static struct Curl_dns_entry *dnsc_entry_assign_https( struct Curl_dns_entry *dns, @@ -601,8 +617,9 @@ struct Curl_dns_entry *Curl_dnsc_mk_https(struct Curl_easy *data, struct dnsc_id id; struct Curl_dns_entry *dns; + (void)data; dnsc_peer2id(&id, CURL_DNST_HTTPS, peer); - dns = dnsc_entry_create(data, &id, FALSE); + dns = dnsc_entry_create(&id, FALSE); dns = dnsc_entry_assign_https(dns, phinfo); return dns; } @@ -610,20 +627,19 @@ struct Curl_dns_entry *Curl_dnsc_mk_https(struct Curl_easy *data, static struct Curl_dns_entry *dnsc_add_https(struct Curl_easy *data, struct Curl_dnscache *dnscache, struct Curl_https_rrinfo **phinfo, - struct dnsc_id *id, - bool permanent) + struct dnsc_id *id) { struct Curl_dns_entry *dns, *dns2; struct dnsc_key key; - dns = dnsc_entry_create(data, id, permanent); + dns = dnsc_entry_create(id, FALSE); dns = dnsc_entry_assign_https(dns, phinfo); if(!dns) return NULL; /* Store the resolved data in our DNS cache. */ dnsc_id2key(&key, id); - dns2 = Curl_hash_add(&dnscache->entries, key.data, key.len, (void *)dns); + dns2 = dnsc_add_entry(data, dnscache, &key, dns); if(!dns2) { dnscache_entry_free(dns); return NULL; @@ -646,13 +662,13 @@ static struct Curl_dns_entry *dnsc_add_addr(struct Curl_easy *data, struct Curl_dns_entry *dns; struct Curl_dns_entry *dns2; - dns = dnsc_entry_create(data, id, permanent); + dns = dnsc_entry_create(id, permanent); dns = dnsc_entry_assign_addr(data, dns, dns_queries, paddr, NULL); if(!dns) return NULL; /* Store the resolved data in our DNS cache. */ - dns2 = Curl_hash_add(&dnscache->entries, key->data, key->len, (void *)dns); + dns2 = dnsc_add_entry(data, dnscache, key, dns); if(!dns2) { dnscache_entry_free(dns); return NULL; @@ -675,14 +691,14 @@ static struct Curl_dns_entry *dnsc_add_peer_addr( struct Curl_dns_entry *dns2; struct dnsc_key key; - dns = dnsc_entry_create(data, id, permanent); + dns = dnsc_entry_create(id, permanent); dns = dnsc_entry_assign_addr(data, dns, dns_queries, paddr, NULL); if(!dns) return NULL; /* Store the resolved data in our DNS cache. */ dnsc_id2key(&key, id); - dns2 = Curl_hash_add(&dnscache->entries, key.data, key.len, (void *)dns); + dns2 = dnsc_add_entry(data, dnscache, &key, dns); if(!dns2) { dnscache_entry_free(dns); return NULL; @@ -712,7 +728,7 @@ CURLcode Curl_dnscache_add(struct Curl_easy *data, /* Store the resolved data in our DNS cache and up ref count */ dnscache_lock(data, dnscache); - if(!Curl_hash_add(&dnscache->entries, key.data, key.len, (void *)entry)) { + if(!dnsc_add_entry(data, dnscache, &key, entry)) { dnscache_unlock(data, dnscache); return CURLE_OUT_OF_MEMORY; } @@ -749,7 +765,7 @@ CURLcode Curl_dnscache_add_negative(struct Curl_easy *data, #ifdef USE_HTTPSRR else if(dns_queries == CURL_DNSQ_HTTPS) { dnsc_peer2id(&id, CURL_DNST_HTTPS, peer); - dns = dnsc_add_https(data, dnscache, NULL, &id, FALSE); + dns = dnsc_add_https(data, dnscache, NULL, &id); if(!dns) result = CURLE_OUT_OF_MEMORY; } diff --git a/lib/vdns/dnscache.h b/lib/vdns/dnscache.h index 73dcff60d70f..bee5f78fb11d 100644 --- a/lib/vdns/dnscache.h +++ b/lib/vdns/dnscache.h @@ -46,16 +46,14 @@ struct Curl_dns_entry { #ifdef USE_HTTPSRR struct Curl_https_rrinfo *hinfo; #endif - /* timestamp == 0 -- permanent CURLOPT_RESOLVE entry (does not time out) */ - struct curltime timestamp; - size_t hostlen; - /* reference counter, entry is freed on reaching 0 */ - uint32_t refcount; - /* hostname port number that resolved to addr. */ - uint16_t port; + struct curltime added; /* time this entry was added to the cache */ + uint32_t refcount; /* reference counter, entry is freed on reaching 0 */ + uint16_t hostlen; /* length of hostname */ + uint16_t port; /* host port */ char type; /* CURL_DNST_ADDR or CURL_DNST_HTTPS */ uint8_t dns_queries; /* CURL_DNSQ_* type of queries performed for this */ uint8_t dns_responses; /* CURL_DNSQ_* type this entry has responses for */ + BIT(permanent); /* entry is permanent, e.g. does not time out */ /* hostname that resolved to addr. may be NULL (Unix domain sockets). */ char hostname[1]; }; @@ -101,7 +99,7 @@ void Curl_dnscache_init(struct Curl_dnscache *dns, size_t size); void Curl_dnscache_destroy(struct Curl_dnscache *dns); /* prune old entries from the DNS cache */ -void Curl_dnscache_prune(struct Curl_easy *data); +void Curl_dnscache_prune(struct Curl_easy *data, const struct curltime *pnow); /* clear the DNS cache */ void Curl_dnscache_clear(struct Curl_easy *data); diff --git a/tests/unit/unit1607.c b/tests/unit/unit1607.c index 72df3808ca67..1d1fedcb1c7d 100644 --- a/tests/unit/unit1607.c +++ b/tests/unit/unit1607.c @@ -23,6 +23,7 @@ ***************************************************************************/ #include "unitcheck.h" #include "urldata.h" +#include "strcase.h" #include "curl_addrinfo.h" static CURLcode t1607_setup(void) @@ -32,6 +33,27 @@ static CURLcode t1607_setup(void) return result; } +struct t1607_key { + uint8_t data[255 + 3]; + size_t len; +}; + +static void t1607_create_key(struct t1607_key *key, + const char *hostname, + uint16_t port, + uint8_t type) +{ + size_t namelen = strlen(hostname); + if(namelen > (sizeof(key->data) - 3)) + namelen = sizeof(key->data) - 3; + /* store and lower case the name */ + key->data[0] = type; + key->data[1] = (uint8_t)((port >> 8) & 0xff); + key->data[2] = (uint8_t)(port & 0xff); + Curl_strntolower((char *)key->data + 3, hostname, namelen); + key->len = namelen + 3; +} + static CURLcode test_unit1607(const char *arg) { /* In builds without IPv6 support CURLOPT_RESOLVE should skip over those @@ -108,7 +130,7 @@ static CURLcode test_unit1607(const char *arg) size_t addressnum = CURL_ARRAYSIZE(tests[i].address); struct Curl_addrinfo *addr; struct Curl_dns_entry *dns; - void *entry_id; + struct t1607_key entry_id; bool problem = FALSE; easy = curl_easy_init(); if(!easy) @@ -127,13 +149,9 @@ static CURLcode test_unit1607(const char *arg) Curl_loadhostpairs(easy); - entry_id = (void *)curl_maprintf("%c%s:%u", CURL_DNST_ADDR, - tests[i].host, tests[i].port); - if(!entry_id) - goto error; + t1607_create_key(&entry_id, tests[i].host, tests[i].port, CURL_DNST_ADDR); dns = Curl_hash_pick(&multi->dnscache.entries, - entry_id, strlen(entry_id) + 1); - curlx_safefree(entry_id); + entry_id.data, entry_id.len); addr = dns ? dns->addr : NULL; @@ -190,18 +208,18 @@ static CURLcode test_unit1607(const char *arg) break; } - if(dns->timestamp.tv_sec && tests[i].permanent) { + if(!dns->permanent && tests[i].permanent) { curl_mfprintf(stderr, - "%s:%d tests[%zu] failed. the timestamp is not zero " + "%s:%d tests[%zu] failed. the permanent bit is not set " "but tests[%zu].permanent is TRUE\n", __FILE__, __LINE__, i, i); problem = TRUE; break; } - if(dns->timestamp.tv_sec == 0 && !tests[i].permanent) { - curl_mfprintf(stderr, "%s:%d tests[%zu] failed. the timestamp is zero " - "but tests[%zu].permanent is FALSE\n", + if(dns->permanent && !tests[i].permanent) { + curl_mfprintf(stderr, "%s:%d tests[%zu] failed. the permanent bit " + "is set but tests[%zu].permanent is FALSE\n", __FILE__, __LINE__, i, i); problem = TRUE; break; From 89ca8fe80ad4ca6434724a4ed287850deabe9766 Mon Sep 17 00:00:00 2001 From: Joshua Rogers Date: Wed, 9 Sep 2026 17:04:38 +0200 Subject: [PATCH 108/481] cw-out: avoid O(n^2) work while buffering paused output Improves paused-output buffer performance without changing protocol behavior. The buffer chain now keeps a tail pointer, so appending does not repeatedly walk the list, and tracks the total buffered length instead of rescanning every buffer on each append. Flushing proceeds from the oldest buffer iteratively. This reduces buffering many paused writes from quadratic list/replay bookkeeping to linear work. Closes #22896 --- lib/cw-out.c | 93 ++++++++++++++++++++++++++-------------------------- 1 file changed, 46 insertions(+), 47 deletions(-) diff --git a/lib/cw-out.c b/lib/cw-out.c index 0137975b1eba..e1f8abe18a94 100644 --- a/lib/cw-out.c +++ b/lib/cw-out.c @@ -97,6 +97,8 @@ static void cw_out_buf_free(struct cw_out_buf *cwbuf) struct cw_out_ctx { struct Curl_cwriter super; struct cw_out_buf *buf; + struct cw_out_buf *tail; + size_t buffered_len; BIT(errored); }; @@ -106,6 +108,8 @@ static CURLcode cw_out_init(struct Curl_easy *data, struct cw_out_ctx *ctx = writer->ctx; (void)data; ctx->buf = NULL; + ctx->tail = NULL; + ctx->buffered_len = 0; return CURLE_OK; } @@ -116,17 +120,8 @@ static void cw_out_bufs_free(struct cw_out_ctx *ctx) cw_out_buf_free(ctx->buf); ctx->buf = next; } -} - -static size_t cw_out_bufs_len(struct cw_out_ctx *ctx) -{ - struct cw_out_buf *cwbuf = ctx->buf; - size_t len = 0; - while(cwbuf) { - len += curlx_dyn_len(&cwbuf->b); - cwbuf = cwbuf->next; - } - return len; + ctx->tail = NULL; + ctx->buffered_len = 0; } static void cw_out_close(struct Curl_easy *data, struct Curl_cwriter *writer) @@ -307,38 +302,34 @@ static CURLcode cw_out_buf_flush(struct cw_out_ctx *ctx, static CURLcode cw_out_flush_chain(struct cw_out_ctx *ctx, struct Curl_easy *data, - struct cw_out_buf **pcwbuf, bool flush_all) { - struct cw_out_buf *cwbuf = *pcwbuf; CURLcode result; - if(!cwbuf) - return CURLE_OK; - if(data->req.writer.paused) - return CURLE_OK; + /* write the chain oldest first until it blocks or gets empty */ + while(ctx->buf) { + struct cw_out_buf *cwbuf = ctx->buf; + size_t blen_before; - /* write the end of the chain until it blocks or gets empty */ - while(cwbuf->next) { - struct cw_out_buf **plast = &cwbuf->next; - while((*plast)->next) - plast = &(*plast)->next; - result = cw_out_flush_chain(ctx, data, plast, flush_all); + if(data->req.writer.paused) + return CURLE_OK; + + blen_before = curlx_dyn_len(&cwbuf->b); + result = cw_out_buf_flush(ctx, data, cwbuf, flush_all); if(result) return result; - if(*plast) { - /* could not write last, paused again? */ + ctx->buffered_len -= blen_before - curlx_dyn_len(&cwbuf->b); + + if(curlx_dyn_len(&cwbuf->b)) { + /* could not write it all, paused again? */ DEBUGASSERT(data->req.writer.paused); return CURLE_OK; } - } - result = cw_out_buf_flush(ctx, data, cwbuf, flush_all); - if(result) - return result; - if(!curlx_dyn_len(&cwbuf->b)) { + ctx->buf = cwbuf->next; + if(!ctx->buf) + ctx->tail = NULL; cw_out_buf_free(cwbuf); - *pcwbuf = NULL; } return CURLE_OK; } @@ -348,25 +339,33 @@ static CURLcode cw_out_append(struct cw_out_ctx *ctx, cw_out_type otype, const char *buf, size_t blen) { + CURLcode result; + CURL_TRC_WRITE(data, "[OUT] paused, buffering %zu more bytes (%zu/%d)", - blen, cw_out_bufs_len(ctx), DYN_PAUSE_BUFFER); - if(cw_out_bufs_len(ctx) + blen > DYN_PAUSE_BUFFER) { + blen, ctx->buffered_len, DYN_PAUSE_BUFFER); + if(ctx->buffered_len + blen > DYN_PAUSE_BUFFER) { failf(data, "pause buffer not large enough -> CURLE_TOO_LARGE"); return CURLE_TOO_LARGE; } - /* if we do not have a buffer, or it is of another type, make a new one. - * For CW_OUT_HDS always make a new one, so we "replay" headers exactly - * as they came in */ - if(!ctx->buf || (ctx->buf->type != otype) || (otype == CW_OUT_HDS)) { + /* if we do not have a buffer, or it is of another type, make a new one + * and append it at the tail. For CW_OUT_HDS always make a new one, so + * we "replay" headers exactly as they came in */ + if(!ctx->tail || (ctx->tail->type != otype) || (otype == CW_OUT_HDS)) { struct cw_out_buf *cwbuf = cw_out_buf_create(otype); if(!cwbuf) return CURLE_OUT_OF_MEMORY; - cwbuf->next = ctx->buf; - ctx->buf = cwbuf; + if(ctx->tail) + ctx->tail->next = cwbuf; + else + ctx->buf = cwbuf; + ctx->tail = cwbuf; } - DEBUGASSERT(ctx->buf && (ctx->buf->type == otype)); - return curlx_dyn_addn(&ctx->buf->b, buf, blen); + DEBUGASSERT(ctx->tail && (ctx->tail->type == otype)); + result = curlx_dyn_addn(&ctx->tail->b, buf, blen); + if(!result) + ctx->buffered_len += blen; + return result; } static CURLcode cw_out_do_write(struct cw_out_ctx *ctx, @@ -377,10 +376,10 @@ static CURLcode cw_out_do_write(struct cw_out_ctx *ctx, { CURLcode result = CURLE_OK; - /* if we have buffered data and it is a different type than what - * we are writing now, try to flush all */ - if(ctx->buf && ctx->buf->type != otype) { - result = cw_out_flush_chain(ctx, data, &ctx->buf, TRUE); + /* if we have buffered data and the last chunk buffered is of a + * different type than what we are writing now, try to flush all */ + if(ctx->tail && ctx->tail->type != otype) { + result = cw_out_flush_chain(ctx, data, TRUE); if(result) goto out; } @@ -390,7 +389,7 @@ static CURLcode cw_out_do_write(struct cw_out_ctx *ctx, result = cw_out_append(ctx, data, otype, buf, blen); if(result) goto out; - result = cw_out_flush_chain(ctx, data, &ctx->buf, flush_all); + result = cw_out_flush_chain(ctx, data, flush_all); if(result) goto out; } @@ -460,7 +459,7 @@ static CURLcode cw_out_do_flush(struct Curl_easy *data, CURLcode result; CURL_TRC_WRITE(data, "[OUT] flush"); - result = cw_out_flush_chain(ctx, data, &ctx->buf, flush_all); + result = cw_out_flush_chain(ctx, data, flush_all); if(result) { ctx->errored = TRUE; cw_out_bufs_free(ctx); From 5fb7e612f3de7b5c8482614985b0a7db006a3e3d Mon Sep 17 00:00:00 2001 From: Joshua Rogers Date: Fri, 11 Sep 2026 08:43:57 +0200 Subject: [PATCH 109/481] dynhds: grow header array geometrically Curl_dynhds_add grew its pointer array by a fixed 16 slots per resize, copying every existing pointer each time and doing O(n^2) aggregate work across n inserts. Grow geometrically instead for amortized O(1) appends. Closes #22910 --- lib/dynhds.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/dynhds.c b/lib/dynhds.c index 097306cec98f..3d00b1510098 100644 --- a/lib/dynhds.c +++ b/lib/dynhds.c @@ -146,7 +146,7 @@ CURLcode Curl_dynhds_add(struct dynhds *dynhds, goto out; if(dynhds->hds_len + 1 >= dynhds->hds_allc) { - size_t nallc = dynhds->hds_len + 16; + size_t nallc = dynhds->hds_allc ? dynhds->hds_allc * 2 : 16; struct dynhds_entry **nhds; if(dynhds->max_entries && nallc > dynhds->max_entries) From 997f66e4656de2ff6db391fd8c1d878e0587726d Mon Sep 17 00:00:00 2001 From: Joshua Rogers Date: Fri, 11 Sep 2026 08:53:53 +0200 Subject: [PATCH 110/481] fnmatch: replace recursive matcher with iterative greedy algorithm The fallback matcher did recursive backtracking and could do O(pattern length * string length^2) work for a non-match. Use iterative greedy matching that backtracks only to the most recent '*', reducing the bound to O(pattern length * string length). Preserve the existing wildcard syntax and two-star-group limit. Closes #22911 --- lib/curl_fnmatch.c | 183 +++++++++++++++++++++++++----------------- tests/unit/unit1307.c | 5 +- 2 files changed, 112 insertions(+), 76 deletions(-) diff --git a/lib/curl_fnmatch.c b/lib/curl_fnmatch.c index d165554f8db5..58475a25f2f6 100644 --- a/lib/curl_fnmatch.c +++ b/lib/curl_fnmatch.c @@ -248,99 +248,132 @@ static int setcharset(const unsigned char **p, unsigned char *charset) return SETCHARSET_FAIL; } -static int loop(const unsigned char *pattern, const unsigned char *string, - int maxstars) +/* match one pattern token against one string character, advancing both */ +static int matchtoken(const unsigned char **pp, const unsigned char **sp) +{ + const unsigned char *p = *pp; + const unsigned char *s = *sp; + + switch(*p) { + case '?': + break; + case '\\': + if(p[1]) + p++; + if(*s != *p) + return 0; + break; + case '[': { + unsigned char charset[CURLFNM_CHSET_SIZE]; + const unsigned char *pp2 = p + 1; /* Copy in case of syntax error. */ + bool found = FALSE; + if(!setcharset(&pp2, charset)) + return -1; /* Syntax error in set; mismatch! */ + if(charset[(unsigned int)*s]) + found = TRUE; + else if(charset[CURLFNM_ALNUM]) + found = ISALNUM(*s); + else if(charset[CURLFNM_ALPHA]) + found = ISALPHA(*s); + else if(charset[CURLFNM_DIGIT]) + found = ISDIGIT(*s); + else if(charset[CURLFNM_XDIGIT]) + found = ISXDIGIT(*s); + else if(charset[CURLFNM_PRINT]) + found = ISPRINT(*s); + else if(charset[CURLFNM_SPACE]) + found = ISBLANK(*s); + else if(charset[CURLFNM_UPPER]) + found = ISUPPER(*s); + else if(charset[CURLFNM_LOWER]) + found = ISLOWER(*s); + else if(charset[CURLFNM_BLANK]) + found = ISBLANK(*s); + else if(charset[CURLFNM_GRAPH]) + found = ISGRAPH(*s); + + if(charset[CURLFNM_NEGATE]) + found = !found; + + if(!found) + return 0; + *pp = pp2 + 1; + *sp = s + 1; + return 1; + } + default: + if(*p != *s) + return 0; + break; + } + *pp = p + 1; + *sp = s + 1; + return 1; +} + +/* greedy match with backtracking to the most recent '*' */ +static int loop(const unsigned char *pattern, const unsigned char *string) { const unsigned char *p = pattern; const unsigned char *s = string; - unsigned char charset[CURLFNM_CHSET_SIZE] = { 0 }; - - for(;;) { - const unsigned char *pp; + const unsigned char *star_p = NULL; + const unsigned char *star_s = NULL; + int maxstars = 2; - switch(*p) { - case '*': + while(*s) { + if(*p == '*') { if(!maxstars) return CURL_FNMATCH_NOMATCH; - /* Regroup consecutive stars and question marks. This can be done because - '*?*?*' can be expressed as '??*'. */ + maxstars--; for(;;) { - if(*++p == '\0') + p++; + if(!*p) return CURL_FNMATCH_MATCH; if(*p == '?') { - if(!*s++) + if(!*s) return CURL_FNMATCH_NOMATCH; + s++; } else if(*p != '*') break; } - /* Skip string characters until we find a match with pattern suffix. */ - for(maxstars--; *s; s++) { - if(loop(p, s, maxstars) == CURL_FNMATCH_MATCH) - return CURL_FNMATCH_MATCH; - } - return CURL_FNMATCH_NOMATCH; - case '?': - if(!*s) - return CURL_FNMATCH_NOMATCH; - s++; - p++; - break; - case '\0': - return *s ? CURL_FNMATCH_NOMATCH : CURL_FNMATCH_MATCH; - case '\\': - if(p[1]) - p++; - if(*s++ != *p++) + star_p = p; + star_s = s; + } + else if(*p) { + const unsigned char *np = p; + const unsigned char *ns = s; + int r = matchtoken(&np, &ns); + if(r < 0) return CURL_FNMATCH_NOMATCH; - break; - case '[': - pp = p + 1; /* Copy in case of syntax error in set. */ - if(setcharset(&pp, charset)) { - bool found = FALSE; - if(!*s) - return CURL_FNMATCH_NOMATCH; - if(charset[(unsigned int)*s]) - found = TRUE; - else if(charset[CURLFNM_ALNUM]) - found = ISALNUM(*s); - else if(charset[CURLFNM_ALPHA]) - found = ISALPHA(*s); - else if(charset[CURLFNM_DIGIT]) - found = ISDIGIT(*s); - else if(charset[CURLFNM_XDIGIT]) - found = ISXDIGIT(*s); - else if(charset[CURLFNM_PRINT]) - found = ISPRINT(*s); - else if(charset[CURLFNM_SPACE]) - found = ISBLANK(*s); - else if(charset[CURLFNM_UPPER]) - found = ISUPPER(*s); - else if(charset[CURLFNM_LOWER]) - found = ISLOWER(*s); - else if(charset[CURLFNM_BLANK]) - found = ISBLANK(*s); - else if(charset[CURLFNM_GRAPH]) - found = ISGRAPH(*s); - - if(charset[CURLFNM_NEGATE]) - found = !found; - - if(!found) - return CURL_FNMATCH_NOMATCH; - p = pp + 1; - s++; - break; + if(r) { + p = np; + s = ns; + continue; } - /* Syntax error in set; mismatch! */ - return CURL_FNMATCH_NOMATCH; - - default: - if(*p++ != *s++) + if(!star_p) return CURL_FNMATCH_NOMATCH; - break; + p = star_p; + s = ++star_s; } + else if(star_p) { + p = star_p; + s = ++star_s; + } + else + return CURL_FNMATCH_NOMATCH; + } + while(*p == '*') { + if(!maxstars) + return CURL_FNMATCH_NOMATCH; + maxstars--; + do { + p++; + if(*p == '?') + return CURL_FNMATCH_NOMATCH; + } while(*p == '*'); } + return *p ? CURL_FNMATCH_NOMATCH : CURL_FNMATCH_MATCH; } /* @@ -354,7 +387,7 @@ int Curl_fnmatch(void *ptr, const char *pattern, const char *string) return CURL_FNMATCH_FAIL; } return loop((const unsigned char *)pattern, - (const unsigned char *)string, 2); + (const unsigned char *)string); } #else /* HAVE_FNMATCH */ diff --git a/tests/unit/unit1307.c b/tests/unit/unit1307.c index c45dc81c770e..9ed570967589 100644 --- a/tests/unit/unit1307.c +++ b/tests/unit/unit1307.c @@ -43,8 +43,8 @@ #define MAC_DIFFER 0x40 #define MAC_SHIFT 16 -#if 0 #define MAC_MATCH ((CURL_FNMATCH_MATCH << MAC_SHIFT) | MAC_DIFFER) +#if 0 #define MAC_NOMATCH ((CURL_FNMATCH_NOMATCH << MAC_SHIFT) | MAC_DIFFER) #endif #define MAC_FAIL ((CURL_FNMATCH_FAIL << MAC_SHIFT) | MAC_DIFFER) @@ -219,6 +219,9 @@ static CURLcode test_unit1307(const char *arg) { "***.txt", "t.txt", MATCH }, { "****.txt", ".txt", MATCH }, + /* custom matcher limits wildcard star groups */ + { "*a*b*c", "abc", NOMATCH|LINUX_MATCH|MAC_MATCH }, + /* empty string or pattern */ { "", "", MATCH }, { "", "hello", NOMATCH }, From 73d15fcb1a7d7645fdc0626312a9f12aa82cf2ff Mon Sep 17 00:00:00 2001 From: Viktor Szakats Date: Fri, 11 Sep 2026 13:26:37 +0200 Subject: [PATCH 111/481] GHA: switch back to Azure apt mirror, strip the rest Hopefully fixing: ``` Get:1 file:/etc/apt/apt-mirrors.txt Mirrorlist [144 B] Hit:2 https://archive.ubuntu.com/ubuntu noble InRelease Error: The action has timed out. ``` Ref: https://github.com/curl/curl/actions/runs/34571926256/job/103175759594#step:2:66 Follow-up to 50ff4f2927e3e319d39ba86bbcac3f57e5c89984 #21414 Closes #22912 --- .github/actions/pkg-install/action.yml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/.github/actions/pkg-install/action.yml b/.github/actions/pkg-install/action.yml index 66633984bd69..ca79d4a0e7f1 100644 --- a/.github/actions/pkg-install/action.yml +++ b/.github/actions/pkg-install/action.yml @@ -26,10 +26,13 @@ runs: sudo rm -f /etc/apt/apt.conf.d/20apt-esm-hook.conf || true ls -l /etc/apt/sources.list.d sudo find /etc/apt/sources.list.d -type f -not -name 'ubuntu.sources' -delete -print - sudo sed -i 's/priority:1/priority:9/' /etc/apt/apt-mirrors.txt; cat /etc/apt/apt-mirrors.txt + sudo sed -i '/\/archive\.ubuntu\.com/d' /etc/apt/apt-mirrors.txt + sudo sed -i '/\/security\.ubuntu\.com/d' /etc/apt/apt-mirrors.txt + # sudo sed -i 's/priority:1/priority:9/' /etc/apt/apt-mirrors.txt + cat /etc/apt/apt-mirrors.txt [[ "${PACKAGES}" = *':i386'* ]] && sudo dpkg --add-architecture i386 - sudo apt-get -o Dpkg::Use-Pty=0 update - sudo apt-get -o Dpkg::Use-Pty=0 install ${PACKAGES} + sudo apt-get -o Dpkg::Use-Pty=0 -o Debug::Acquire::http=1 -o Debug::Acquire::https=1 update + sudo apt-get -o Dpkg::Use-Pty=0 -o Debug::Acquire::http=1 -o Debug::Acquire::https=1 install ${PACKAGES} - name: 'install (brew)' if: ${{ inputs.brew }} From 6cc366a351a8665a23bb1b1c62775639d622886c Mon Sep 17 00:00:00 2001 From: Viktor Szakats Date: Fri, 11 Sep 2026 13:28:48 +0200 Subject: [PATCH 112/481] CI: tidy up - GHA/configure-vs-cmake: drop `./` prefixes from commands. - GHA/linux: drop redundant mirror tweak from Intel C prereqs step. This tweak is done earlier as part of the main prereqs step. - GHA/linux: option order. - GHA/windows: drop duplicate `.pc` config dump from MSVC jobs. - appveyor.sh: add trailing slash to URL. - appveyor.sh: prefer single-quotes to match rest of script. - appveyor.sh: replace heredoc with printf in block. - appveyor.yml: quote truthy value. Closes #22913 --- .github/workflows/configure-vs-cmake.yml | 18 +++++++++--------- .github/workflows/linux.yml | 4 +--- .github/workflows/windows.yml | 6 ------ appveyor.sh | 20 ++++++++++---------- appveyor.yml | 2 +- 5 files changed, 21 insertions(+), 29 deletions(-) diff --git a/.github/workflows/configure-vs-cmake.yml b/.github/workflows/configure-vs-cmake.yml index 403c021a7b01..3f2e691261de 100644 --- a/.github/workflows/configure-vs-cmake.yml +++ b/.github/workflows/configure-vs-cmake.yml @@ -76,13 +76,13 @@ jobs: done - name: 'compare generated curl_config.h files' - run: ./.github/scripts/cmp-config.pl bld-am/lib/curl_config.h bld-cm/lib/curl_config.h + run: .github/scripts/cmp-config.pl bld-am/lib/curl_config.h bld-cm/lib/curl_config.h - name: 'compare generated libcurl.pc files' - run: ./.github/scripts/cmp-pkg-config.sh bld-am/libcurl.pc bld-cm/libcurl.pc + run: .github/scripts/cmp-pkg-config.sh bld-am/libcurl.pc bld-cm/libcurl.pc - name: 'compare generated curl-config files' - run: ./.github/scripts/cmp-pkg-config.sh bld-am/curl-config bld-cm/curl-config + run: .github/scripts/cmp-pkg-config.sh bld-am/curl-config bld-cm/curl-config check-macos: name: 'macOS' @@ -131,13 +131,13 @@ jobs: pccritic --min-score 98 bld-cm/libcurl.pc - name: 'compare generated curl_config.h files' - run: ./.github/scripts/cmp-config.pl bld-am/lib/curl_config.h bld-cm/lib/curl_config.h + run: .github/scripts/cmp-config.pl bld-am/lib/curl_config.h bld-cm/lib/curl_config.h - name: 'compare generated libcurl.pc files' - run: ./.github/scripts/cmp-pkg-config.sh bld-am/libcurl.pc bld-cm/libcurl.pc + run: .github/scripts/cmp-pkg-config.sh bld-am/libcurl.pc bld-cm/libcurl.pc - name: 'compare generated curl-config files' - run: ./.github/scripts/cmp-pkg-config.sh bld-am/curl-config bld-cm/curl-config + run: .github/scripts/cmp-pkg-config.sh bld-am/curl-config bld-cm/curl-config check-windows: name: 'Windows' @@ -183,10 +183,10 @@ jobs: done - name: 'compare generated curl_config.h files' - run: ./.github/scripts/cmp-config.pl bld-am/lib/curl_config.h bld-cm/lib/curl_config.h + run: .github/scripts/cmp-config.pl bld-am/lib/curl_config.h bld-cm/lib/curl_config.h - name: 'compare generated libcurl.pc files' - run: ./.github/scripts/cmp-pkg-config.sh bld-am/libcurl.pc bld-cm/libcurl.pc + run: .github/scripts/cmp-pkg-config.sh bld-am/libcurl.pc bld-cm/libcurl.pc - name: 'compare generated curl-config files' - run: ./.github/scripts/cmp-pkg-config.sh bld-am/curl-config bld-cm/curl-config + run: .github/scripts/cmp-pkg-config.sh bld-am/curl-config bld-cm/curl-config diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index ee2ebe47ffad..cdec7063ff86 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -222,7 +222,7 @@ jobs: install_packages: gcc-16 libidn2-dev libssh2-1-dev libnghttp2-dev libldap-dev valgrind CC: gcc-16 tflags: '--subset=0/2' - generate: -DENABLE_DEBUG=ON -DENABLE_THREADED_RESOLVER=OFF -DCURL_GCC_ANALYZER=ON -DCURL_ENABLE_NTLM=ON + generate: -DENABLE_DEBUG=ON -DENABLE_THREADED_RESOLVER=OFF -DCURL_ENABLE_NTLM=ON -DCURL_GCC_ANALYZER=ON - name: 'openssl libssh2 sync-resolver valgrind 2' image: ubuntu-26.04-arm @@ -825,8 +825,6 @@ jobs: curl --disable --fail --silent --show-error --connect-timeout 15 --max-time 120 --retry 6 --retry-connrefused \ --compressed https://apt.repos.intel.com/intel-gpg-keys/GPG-PUB-KEY-INTEL-SW-PRODUCTS.PUB | \ sudo tee /etc/apt/trusted.gpg.d/intel-sw.asc >/dev/null - sudo find /etc/apt/sources.list.d -type f -not -name 'ubuntu.sources' -delete -print - sudo sed -i 's/priority:1/priority:9/' /etc/apt/apt-mirrors.txt; cat /etc/apt/apt-mirrors.txt sudo add-apt-repository 'deb https://apt.repos.intel.com/oneapi all main' sudo apt-get -o Dpkg::Use-Pty=0 install intel-oneapi-compiler-dpcpp-cpp-and-cpp-classic source /opt/intel/oneapi/setvars.sh diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 11703522bbec..1469e10a679d 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -1156,12 +1156,6 @@ jobs: timeout-minutes: 5 run: cmake --build bld --config "${MATRIX_TYPE}" --parallel 5 - - name: 'libcurl.pc, curl-config' - run: | - for f in libcurl.pc curl-config; do - echo "::group::${f}"; grep -v '^#' bld/"${f}" || true; echo '::endgroup::' - done - - name: 'curl -V' timeout-minutes: 1 run: | diff --git a/appveyor.sh b/appveyor.sh index 3c2952cfdd04..52f1252c4a59 100644 --- a/appveyor.sh +++ b/appveyor.sh @@ -118,21 +118,21 @@ fi if [[ "${CREATE_ARTIFACT:-}" = 'true' ]]; then cp /usr/ssl/certs/ca-bundle.crt curl-ca-bundle.crt - echo "Checking that https works (it should find curl-ca-bundle.crt if needed)" - "${curl}" -v -fsS --retry 6 --retry-all-errors -o /dev/null https://curl.se + echo 'Checking that https works (it should find curl-ca-bundle.crt if needed)' + "${curl}" -v -fsS --retry 6 --retry-all-errors -o /dev/null https://curl.se/ if [ -n "${APPVEYOR_PULL_REQUEST_NUMBER:-}" ]; then archive="curl_pr${APPVEYOR_PULL_REQUEST_NUMBER}_${APPVEYOR_PULL_REQUEST_HEAD_COMMIT}.zip" else archive="curl_${APPVEYOR_REPO_COMMIT}.zip" fi -cat << EOF > WARNING.txt -WARNING: Do not run this build unless the download link was provided by staff. -Anyone can submit a PR with possibly malicious code to generate a build. -${archive} -EOF - echo "Finding curl module dependencies" - "${curl}" --dump-module-paths | grep -Fv "C:\Windows" | tee > files.tmp - echo "Creating artifact" + { + echo 'WARNING: Do not run this build unless the download link was provided by staff.' + echo 'Anyone can submit a PR with possibly malicious code to generate a build.' + echo "${archive}" + } > WARNING.txt + echo 'Finding curl module dependencies' + "${curl}" --dump-module-paths | grep -Fv 'C:\Windows' | tee > files.tmp + echo 'Creating artifact' 7z a "${archive}" -y -bb1 -bsp0 -mx9 -tzip -i@files.tmp curl-ca-bundle.crt WARNING.txt rm files.tmp appveyor PushArtifact "${archive}" diff --git a/appveyor.yml b/appveyor.yml index ff2414b75109..ce63a060debf 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -94,7 +94,7 @@ environment: CMAKE_SHA256: dfc2b2afac257555e3b9ce375b12b2883964283a366c17fec96cf4d17e4f1677 CMAKE_GENERATOR: 'Visual Studio 18 2026' CMAKE_GENERATE: '-A x64 -T ClangCl -DBUILD_SHARED_LIBS=OFF -DCURL_USE_OPENSSL=ON -DENABLE_UNICODE=ON' - CREATE_ARTIFACT: true + CREATE_ARTIFACT: 'true' - job_name: 'CM VS2022, Release, x64, Schannel, Shared, Unicode, Build-tests' APPVEYOR_BUILD_WORKER_IMAGE: 'Visual Studio 2022' From 04bfe8fbbf552a3baf38c629bf9dd59916b3c559 Mon Sep 17 00:00:00 2001 From: Viktor Szakats Date: Fri, 11 Sep 2026 23:14:22 +0200 Subject: [PATCH 113/481] GHA: enforce egress firewall for the labeler workflow Preview feature, configuration details are not finalized yet. Ref: https://github.com/github-early-access/actions-native-egress-firewall/commit/193ae9e6cc15eeb440b9708caa242a896effaf6a Follow-up to b3e1bfcc46fff3f5a207b981ce32bb16770dcc76 #22867 Follow-up to e5e1c356107ea4f8208757deb69776d94496cfae #22864 Closes #22919 --- .github/egress-firewall.yaml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 .github/egress-firewall.yaml diff --git a/.github/egress-firewall.yaml b/.github/egress-firewall.yaml new file mode 100644 index 000000000000..1cf7ba127a7e --- /dev/null +++ b/.github/egress-firewall.yaml @@ -0,0 +1,15 @@ +# Copyright (C) Viktor Szakats +# +# SPDX-License-Identifier: curl + +# https://github.com/github-early-access/actions-native-egress-firewall + +# Defaults: +# - github.com +# - [...]blob.core.windows.net +# - codeload.github.com +# - [...]actions.githubusercontent.com + +mode: enforce +allow: + - api.github.com From a935481d4e04690ec2eef266528c8f5aacdc3608 Mon Sep 17 00:00:00 2001 From: Jay Satiro Date: Thu, 3 Sep 2026 14:51:33 -0400 Subject: [PATCH 114/481] lib: CURL_GLOBAL_WIN32 -> CURL_GLOBAL_WINSOCK - Change curl_global_init flag name from CURL_GLOBAL_WIN32 to CURL_GLOBAL_WINSOCK. - Retain CURL_GLOBAL_WIN32 as an alias for legacy reasons. - Remove lwIP initialization from CURL_GLOBAL_WINSOCK. Prior to this change lwIP init was controlled by the flag which did not make sense. It may have some historical reason but if it does then that should be represented by a different flag. AFAICT lwIP init is not our responsibility, since it may need different init depending on how it is used. The name change WIN32 -> WINSOCK is to eliminate user confusion. The name WIN32 may imply a broader Windows initialization, which libcurl always does and is not controlled by this flag. Reported-by: Viktor Szakats Ref: https://github.com/curl/curl/pull/22813#issuecomment-5529065833 Closes https://github.com/curl/curl/pull/22819 --- docs/libcurl/curl_global_init.md | 10 +++++++--- docs/libcurl/libcurl-tutorial.md | 4 ++-- docs/libcurl/symbols-in-versions | 3 ++- include/curl/curl.h | 5 +++-- lib/easy.c | 10 +++------- projects/OS400/curl.inc.in | 2 +- 6 files changed, 18 insertions(+), 16 deletions(-) diff --git a/docs/libcurl/curl_global_init.md b/docs/libcurl/curl_global_init.md index c57daa534f22..9347bbb5aea5 100644 --- a/docs/libcurl/curl_global_init.md +++ b/docs/libcurl/curl_global_init.md @@ -84,22 +84,26 @@ backend libcurl uses. Doing TLS based transfers without having the TLS layer initialized may lead to unexpected behaviors. -## CURL_GLOBAL_WIN32 +## CURL_GLOBAL_WINSOCK -Initialize the Win32 socket libraries. +Initialize the Windows sockets library (Winsock). The implication here is that if this bit is not set, the initialization of Winsock has to be done by the application or you risk getting undefined behaviors. This option exists for when the initialization is handled outside of libcurl so there is no need for libcurl to do it again. +Prior to 8.23.0 this flag was called CURL_GLOBAL_WIN32. The name was changed +to eliminate confusion. Most of libcurl's Windows initialization is required +and is not controlled by this flag. + ## CURL_GLOBAL_NOTHING Initialize nothing extra. This sets no bit. ## CURL_GLOBAL_DEFAULT -A sensible default. It initializes both SSL and Win32. Right now, this equals +A sensible default. It initializes both SSL and Winsock. Right now, this equals the functionality of the **CURL_GLOBAL_ALL** mask. ## CURL_GLOBAL_ACK_EINTR diff --git a/docs/libcurl/libcurl-tutorial.md b/docs/libcurl/libcurl-tutorial.md index befcf7739968..227f71a76299 100644 --- a/docs/libcurl/libcurl-tutorial.md +++ b/docs/libcurl/libcurl-tutorial.md @@ -109,10 +109,10 @@ initialize. Using *CURL_GLOBAL_ALL* makes it initialize all known internal sub modules, and might be a good default option. The current two bits that are specified are: -## CURL_GLOBAL_WIN32 +## CURL_GLOBAL_WINSOCK which only does anything on Windows machines. When used on a Windows machine, -it makes libcurl initialize the Win32 socket stuff. Without having that +it makes libcurl initialize the Windows socket stuff. Without having that initialized properly, your program cannot use sockets properly. You should only do this once for each application, so if your program already does this or of another library in use does it, you should not tell libcurl to do this diff --git a/docs/libcurl/symbols-in-versions b/docs/libcurl/symbols-in-versions index 65279d642659..6ac6fa5b3499 100644 --- a/docs/libcurl/symbols-in-versions +++ b/docs/libcurl/symbols-in-versions @@ -44,7 +44,8 @@ CURL_GLOBAL_ALL 7.8 CURL_GLOBAL_DEFAULT 7.8 CURL_GLOBAL_NOTHING 7.8 CURL_GLOBAL_SSL 7.8 -CURL_GLOBAL_WIN32 7.8.1 +CURL_GLOBAL_WIN32 7.8.1 8.23.0 +CURL_GLOBAL_WINSOCK 8.23.0 CURL_HAS_DECLSPEC_ATTRIBUTE 8.13.0 CURL_HET_DEFAULT 7.59.0 CURL_HTTP_VERSION_1_0 7.9.1 diff --git a/include/curl/curl.h b/include/curl/curl.h index b904e4953064..19e1f40e2bdb 100644 --- a/include/curl/curl.h +++ b/include/curl/curl.h @@ -3042,8 +3042,9 @@ typedef enum { } curl_closepolicy; #define CURL_GLOBAL_SSL (1 << 0) /* no purpose since 7.57.0 */ -#define CURL_GLOBAL_WIN32 (1 << 1) -#define CURL_GLOBAL_ALL (CURL_GLOBAL_SSL | CURL_GLOBAL_WIN32) +#define CURL_GLOBAL_WINSOCK (1 << 1) +#define CURL_GLOBAL_WIN32 CURL_GLOBAL_WINSOCK /* for compatibility */ +#define CURL_GLOBAL_ALL (CURL_GLOBAL_SSL | CURL_GLOBAL_WINSOCK) #define CURL_GLOBAL_NOTHING 0 #define CURL_GLOBAL_DEFAULT CURL_GLOBAL_ALL #define CURL_GLOBAL_ACK_EINTR (1 << 2) diff --git a/lib/easy.c b/lib/easy.c index 2674c24712aa..b92f4021567e 100644 --- a/lib/easy.c +++ b/lib/easy.c @@ -159,20 +159,16 @@ static CURLcode global_init(long flags, bool memoryfuncs) } #endif -#ifdef _WIN32 - /* CURL_GLOBAL_WIN32 controls the *optional* part of the initialization which - is for Winsock at the moment. */ - if(flags & CURL_GLOBAL_WIN32) { #ifdef USE_WINSOCK + /* CURL_GLOBAL_WINSOCK controls the *optional* part of the Windows + initialization which is for the Windows sockets library (Winsock). */ + if(flags & CURL_GLOBAL_WINSOCK) { WSADATA wsa; if(WSAStartup(MAKEWORD(2, 2), &wsa)) { DEBUGF(curl_mfprintf(stderr, "Error: WSAStartup() failed\n")); goto fail; } winsock_initialized = TRUE; -#elif defined(USE_LWIPSOCK) - lwip_init(); -#endif } #endif diff --git a/projects/OS400/curl.inc.in b/projects/OS400/curl.inc.in index 5b21429fed91..39fe4a04e053 100644 --- a/projects/OS400/curl.inc.in +++ b/projects/OS400/curl.inc.in @@ -79,7 +79,7 @@ * d CURL_GLOBAL_SSL... d c X'00000001' - d CURL_GLOBAL_WIN32... + d CURL_GLOBAL_WINSOCK... d c X'00000002' d CURL_GLOBAL_ALL... d c X'00000003' From 7bbaf2fffd947a1b7d1b96993caf00b9452200bd Mon Sep 17 00:00:00 2001 From: Daniel Stenberg Date: Thu, 10 Sep 2026 14:29:59 +0200 Subject: [PATCH 115/481] SMB: drop support Closes #22652 --- .circleci/config.yml | 5 +- .github/labeler.yml | 8 - .github/scripts/pyspelling.words | 2 - .github/workflows/checksrc.yml | 2 - .github/workflows/http3-linux.yml | 10 - .github/workflows/linux.yml | 18 +- .github/workflows/macos.yml | 9 - .github/workflows/windows.yml | 13 - CMakeLists.txt | 6 - README.md | 2 +- RELEASE-NOTES | 1 - configure.ac | 23 - docs/CURL-DISABLE.md | 4 - docs/FAQ.md | 2 +- docs/FEATURES.md | 7 - docs/HISTORY.md | 2 + docs/INSTALL-CMAKE.md | 1 - docs/MANUAL.md | 9 - docs/TODO.md | 7 - docs/URL-SYNTAX.md | 17 +- docs/cmdline-opts/_DESCRIPTION.md | 4 +- docs/cmdline-opts/_PROTOCOLS.md | 2 - docs/libcurl/opts/CURLINFO_PROTOCOL.md | 4 +- docs/libcurl/opts/CURLOPT_DEFAULT_PROTOCOL.md | 2 +- docs/libcurl/opts/CURLOPT_FILETIME.md | 1 - docs/libcurl/opts/CURLOPT_PROTOCOLS.md | 8 - docs/libcurl/opts/CURLOPT_PROTOCOLS_STR.md | 5 +- docs/libcurl/opts/CURLOPT_REDIR_PROTOCOLS.md | 8 - .../opts/CURLOPT_REDIR_PROTOCOLS_STR.md | 9 +- docs/libcurl/symbols-in-versions | 4 +- docs/tests/FILEFORMAT.md | 3 - docs/tests/TEST-SUITE.md | 3 +- lib/Makefile.inc | 2 - lib/curl_config-cmake.h.in | 3 - lib/protocol.c | 33 - lib/protocol.h | 5 - lib/smb.c | 1243 ----------------- lib/smb.h | 31 - lib/urldata.h | 1 - lib/version.c | 6 - projects/OS400/README.OS400 | 1 - scripts/cd2nroff | 2 - tests/Makefile.am | 2 - tests/data/Makefile.am | 5 - tests/data/test1451 | 38 - tests/data/test1627 | 2 +- tests/data/test1850 | 39 - tests/requirements.txt | 5 - tests/serverhelp.pm | 2 +- tests/servers.pm | 136 +- tests/smbserver.py | 440 ------ tests/unit/unit1627.c | 9 +- 52 files changed, 37 insertions(+), 2169 deletions(-) delete mode 100644 lib/smb.c delete mode 100644 lib/smb.h delete mode 100644 tests/data/test1451 delete mode 100644 tests/data/test1850 delete mode 100644 tests/requirements.txt delete mode 100755 tests/smbserver.py diff --git a/.circleci/config.yml b/.circleci/config.yml index 2f16bebe3b45..b34474d1e2fa 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -45,9 +45,7 @@ commands: steps: - run: command: | - sudo apt-get update && sudo apt-get install -y libpsl-dev libbrotli-dev libzstd-dev zlib1g-dev python3-pip - python3 -m venv ~/venv - ~/venv/bin/pip --disable-pip-version-check --no-input --no-cache-dir install --progress-bar off --prefer-binary -r tests/requirements.txt + sudo apt-get update && sudo apt-get install -y libpsl-dev libbrotli-dev libzstd-dev zlib1g-dev configure: steps: @@ -104,7 +102,6 @@ commands: steps: - run: command: | - source ~/venv/bin/activate # Revert a CircleCI-specific local setting that makes test 1459 # return 67 (CURLE_LOGIN_DENIED) instead of the # expected 60 (CURLE_PEER_FAILED_VERIFICATION). diff --git a/.github/labeler.yml b/.github/labeler.yml index 0b7dedfea740..f06ef69df698 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -454,14 +454,6 @@ script: scripts/**\ }" -SMB: - - all: - - changed-files: - - any-glob-to-all-files: "{\ - lib/smb.*,\ - tests/smbserver.py\ - }" - SMTP: - all: - changed-files: diff --git a/.github/scripts/pyspelling.words b/.github/scripts/pyspelling.words index ce0f4cbfca3b..e42c621acb58 100644 --- a/.github/scripts/pyspelling.words +++ b/.github/scripts/pyspelling.words @@ -792,9 +792,7 @@ slist sln Slowloris SMB -smb SMBS -smbs SMBv SMTP smtp diff --git a/.github/workflows/checksrc.yml b/.github/workflows/checksrc.yml index 5b199df1c246..1b82b70d23d5 100644 --- a/.github/workflows/checksrc.yml +++ b/.github/workflows/checksrc.yml @@ -80,7 +80,6 @@ jobs: pip: | -r .github/scripts/requirements.txt -r tests/http/requirements.txt - -r tests/requirements.txt - name: 'REUSE check' background: true @@ -142,7 +141,6 @@ jobs: pytype==2024.10.11 -r .github/scripts/requirements.txt -r tests/http/requirements.txt - -r tests/requirements.txt - name: 'check' run: | diff --git a/.github/workflows/http3-linux.yml b/.github/workflows/http3-linux.yml index 69d7061bd4be..04af3374581d 100644 --- a/.github/workflows/http3-linux.yml +++ b/.github/workflows/http3-linux.yml @@ -840,21 +840,12 @@ jobs: make -C bld V=1 -C tests fi - - name: 'install test prereqs' - if: ${{ !contains(matrix.build.install_steps, 'skipall') && !contains(matrix.build.install_steps, 'skiprun') }} - run: | - python3 -m venv ~/venv - if bld/src/curl --disable -V 2>/dev/null | grep smb; then - ~/venv/bin/pip --disable-pip-version-check --no-input --no-cache-dir install --progress-bar off --prefer-binary -r tests/requirements.txt - fi - - name: 'run tests' if: ${{ !contains(matrix.build.install_steps, 'skipall') && !contains(matrix.build.install_steps, 'skiprun') }} env: TFLAGS: '${{ matrix.build.tflags }}' run: | TFLAGS+=' -n' - source ~/venv/bin/activate if [ "${MATRIX_BUILD}" = 'cmake' ]; then cmake --build bld --verbose --target test-ci else @@ -865,7 +856,6 @@ jobs: if: ${{ !contains(matrix.build.install_steps, 'skipall') && !contains(matrix.build.install_steps, 'skiprun') }} run: | export TFLAGS='-j6 --min=4 HTTP/3' - source ~/venv/bin/activate if [ "${MATRIX_BUILD}" = 'cmake' ]; then cmake --build bld --verbose --target test-ci else diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index cdec7063ff86..e728e44a489e 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -78,7 +78,7 @@ jobs: - name: 'awslc' install_steps: awslc pytest LDFLAGS: -Wl,-rpath,/home/runner/awslc/lib - configure: --with-openssl=/home/runner/awslc --enable-ech --enable-ntlm --enable-smb + configure: --with-openssl=/home/runner/awslc --enable-ech --enable-ntlm - name: 'awslc' install_packages: libidn2-dev @@ -271,7 +271,7 @@ jobs: - name: 'openssl !ipv6 !--libcurl !--digest-auth' image: ubuntu-24.04-arm - configure: --with-openssl --disable-ipv6 --enable-debug --disable-unity --disable-libcurl-option --disable-digest-auth --enable-ntlm --enable-smb + configure: --with-openssl --disable-ipv6 --enable-debug --disable-unity --disable-libcurl-option --disable-digest-auth --enable-ntlm - name: 'curl_global_init_mem debug valgrind' image: ubuntu-24.04-arm @@ -280,7 +280,7 @@ jobs: configure: >- --enable-init-mem-debug --with-openssl --disable-debug --enable-unity - --enable-ntlm --enable-smb + --enable-ntlm - name: 'openssl https-only' image: ubuntu-24.04-arm @@ -291,7 +291,7 @@ jobs: --disable-imap --disable-pop3 --disable-smtp --disable-rtsp --without-libssh2 --without-libssh - --disable-tftp --disable-ftp --disable-file --disable-smb + --disable-tftp --disable-ftp --disable-file --enable-ntlm - name: 'openssl torture 1' @@ -433,7 +433,7 @@ jobs: - name: 'event-based' install_packages: libssh-dev - configure: --enable-debug --enable-static --disable-shared --disable-threaded-resolver --with-libssh --with-openssl --enable-ntlm --enable-smb + configure: --enable-debug --enable-static --disable-shared --disable-threaded-resolver --with-libssh --with-openssl --enable-ntlm tflags: '-n --test-event --min=1420' - name: 'duphandle' @@ -926,14 +926,6 @@ jobs: make -C bld V=1 -C tests fi - - name: 'install test prereqs' - if: ${{ !contains(matrix.build.install_steps, 'skipall') && !contains(matrix.build.install_steps, 'skiprun') && matrix.build.container == null }} - run: | - python3 -m venv ~/venv - if bld/src/curl --disable -V 2>/dev/null | grep smb; then - ~/venv/bin/pip --disable-pip-version-check --no-input --no-cache-dir install --progress-bar off --prefer-binary -r tests/requirements.txt - fi - - name: 'run tests' if: ${{ !contains(matrix.build.install_steps, 'skipall') && !contains(matrix.build.install_steps, 'skiprun') }} timeout-minutes: ${{ contains(matrix.build.install_packages, 'valgrind') && 20 || 10 }} diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index a2037e725a07..8c4a8d331240 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -544,14 +544,6 @@ jobs: make -C bld V=1 -C tests fi - - name: 'install test prereqs' - if: ${{ !contains(matrix.build.install_steps, 'skipall') && !contains(matrix.build.install_steps, 'skiprun') }} - run: | - python3 -m venv ~/venv - if bld/src/curl --disable -V 2>/dev/null | grep smb; then - ~/venv/bin/pip --disable-pip-version-check --no-input --no-cache-dir install --progress-bar off --prefer-binary -r tests/requirements.txt - fi - - name: 'run tests' if: ${{ !contains(matrix.build.install_steps, 'skipall') && !contains(matrix.build.install_steps, 'skiprun') }} timeout-minutes: ${{ contains(matrix.build.install_steps, 'torture') && 20 || 10 }} @@ -563,7 +555,6 @@ jobs: if [ "${TEST_TARGET}" != 'test-ci' ]; then TFLAGS+=' --buildinfo' # only test-ci sets this by default, set it manually for test-torture fi - source ~/venv/bin/activate if [[ "${MATRIX_INSTALL_STEPS}" = *'codeset-test1'* ]]; then locale || true unset LANG diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 1469e10a679d..88fb0d1f997d 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -767,14 +767,6 @@ jobs: key: ${{ runner.os }}-stunnel-${{ env.STUNNEL_VERSION }}-amd64 fail-on-cache-miss: true - - name: 'install test prereqs' - if: ${{ matrix.tflags != 'skipall' && matrix.tflags != 'skiprun' }} - timeout-minutes: 2 - run: | - if bld/src/curl.exe --disable -V 2>/dev/null | grep smb; then - python3 -m pip --disable-pip-version-check --no-input --no-cache-dir install --progress-bar off --prefer-binary -r tests/requirements.txt - fi - - name: 'run tests' if: ${{ matrix.tflags != 'skipall' && matrix.tflags != 'skiprun' }} timeout-minutes: 10 @@ -1003,8 +995,6 @@ jobs: image: 'windows-11-arm' openssh: 'OpenSSH-Windows' tflags: '--min=1720' - # leave SMB disabled to save 30-60 seconds by omitting prereqs, - # to counteract the slower test run step config: >- -DENABLE_DEBUG=ON -DCURL_USE_SCHANNEL=ON @@ -1204,9 +1194,6 @@ jobs: sha256sum pkg.bin && sha256sum pkg.bin | grep -qwF -- "${OPENSSH_WINDOWS_SHA256_WIN64}" && unzip pkg.bin && rm -f pkg.bin fi fi - if "bld/src/${MATRIX_TYPE}/curl.exe" --disable -V 2>/dev/null | grep smb; then - python3 -m pip --disable-pip-version-check --no-input --no-cache-dir install --progress-bar off --prefer-binary -r tests/requirements.txt - fi - name: 'run tests' if: ${{ matrix.tflags != 'skipall' && matrix.tflags != 'skiprun' }} diff --git a/CMakeLists.txt b/CMakeLists.txt index 4946e6ce32c7..38af2de7ebd4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -498,8 +498,6 @@ option(CURL_DISABLE_SHA512_256 "Disable SHA-512/256 hash algorithm" OFF) mark_as_advanced(CURL_DISABLE_SHA512_256) option(CURL_DISABLE_SHUFFLE_DNS "Disable shuffle DNS feature" OFF) mark_as_advanced(CURL_DISABLE_SHUFFLE_DNS) -option(CURL_ENABLE_SMB "Enable SMB" OFF) -mark_as_advanced(CURL_ENABLE_SMB) option(CURL_DISABLE_SMTP "Disable SMTP" OFF) mark_as_advanced(CURL_DISABLE_SMTP) option(CURL_DISABLE_SOCKETPAIR "Disable use of socketpair for curl_multi_poll()" OFF) @@ -1992,10 +1990,6 @@ curl_add_if("POP3" NOT CURL_DISABLE_POP3) curl_add_if("POP3S" NOT CURL_DISABLE_POP3 AND _ssl_enabled) curl_add_if("IMAP" NOT CURL_DISABLE_IMAP) curl_add_if("IMAPS" NOT CURL_DISABLE_IMAP AND _ssl_enabled) -curl_add_if("SMB" CURL_ENABLE_SMB AND - _use_curl_ntlm_core AND (SIZEOF_CURL_OFF_T GREATER 4)) -curl_add_if("SMBS" CURL_ENABLE_SMB AND _ssl_enabled AND - _use_curl_ntlm_core AND (SIZEOF_CURL_OFF_T GREATER 4)) curl_add_if("SMTP" NOT CURL_DISABLE_SMTP) curl_add_if("SMTPS" NOT CURL_DISABLE_SMTP AND _ssl_enabled) curl_add_if("SCP" USE_LIBSSH2 OR USE_LIBSSH) diff --git a/README.md b/README.md index 9f77de607be2..f8061f2edc0b 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ SPDX-License-Identifier: curl curl is a command-line tool for transferring data from or to a server using URLs. It supports these protocols: DICT, FILE, FTP, FTPS, GOPHER, GOPHERS, HTTP, HTTPS, IMAP, IMAPS, LDAP, LDAPS, MQTT, MQTTS, POP3, POP3S, RTSP, SCP, -SFTP, SMB, SMBS, SMTP, SMTPS, TELNET, TFTP, WS and WSS. +SFTP, SMTP, SMTPS, TELNET, TFTP, WS and WSS. Learn how to use curl by reading [the man page](https://curl.se/docs/manpage.html) or [everything diff --git a/RELEASE-NOTES b/RELEASE-NOTES index eb103545993a..b326c359e738 100644 --- a/RELEASE-NOTES +++ b/RELEASE-NOTES @@ -85,7 +85,6 @@ Planned upcoming removals include: o HTTP/2 Server Push o local crypto implementations o NTLM - o SMB See https://curl.se/dev/deprecate.html diff --git a/configure.ac b/configure.ac index 87927f3ec51b..a2843b4310e6 100644 --- a/configure.ac +++ b/configure.ac @@ -1058,23 +1058,6 @@ AS_HELP_STRING([--disable-imap],[Disable IMAP support]), AC_MSG_RESULT(yes) ) -AC_MSG_CHECKING([whether to support smb]) -AC_ARG_ENABLE(smb, -AS_HELP_STRING([--enable-smb],[Enable SMB support]) -AS_HELP_STRING([--disable-smb],[Disable SMB support]), -[ case "$enableval" in - yes) - AC_MSG_RESULT(yes) - AC_DEFINE(CURL_ENABLE_SMB, 1, [to enable SMB]) - CURL_ENABLE_SMB=1 - ;; - *) - AC_MSG_RESULT(no) - ;; - esac ], - AC_MSG_RESULT(no) -) - AC_MSG_CHECKING([whether to support smtp]) AC_ARG_ENABLE(smtp, AS_HELP_STRING([--enable-smtp],[Enable SMTP support]) @@ -5477,12 +5460,6 @@ if test "$CURL_DISABLE_IMAP" != "1"; then SUPPORT_PROTOCOLS="$SUPPORT_PROTOCOLS IMAPS" fi fi -if test "$CURL_ENABLE_SMB" = "1" && test "$use_curl_ntlm_core" = "yes"; then - SUPPORT_PROTOCOLS="$SUPPORT_PROTOCOLS SMB" - if test "$SSL_ENABLED" = "1"; then - SUPPORT_PROTOCOLS="$SUPPORT_PROTOCOLS SMBS" - fi -fi if test "$CURL_DISABLE_SMTP" != "1"; then SUPPORT_PROTOCOLS="$SUPPORT_PROTOCOLS SMTP" if test "$SSL_ENABLED" = "1"; then diff --git a/docs/CURL-DISABLE.md b/docs/CURL-DISABLE.md index 83494cf18b4f..f3ba9cc8a977 100644 --- a/docs/CURL-DISABLE.md +++ b/docs/CURL-DISABLE.md @@ -161,10 +161,6 @@ Disable the SHA-512/256 hash algorithm. Disable the shuffle DNS feature -## `CURL_ENABLE_SMB` - -Enable the SMB(S) protocols - ## `CURL_DISABLE_SMTP` Disable the SMTP(S) protocols diff --git a/docs/FAQ.md b/docs/FAQ.md index 2cc5c68ad446..fbf26f1baf17 100644 --- a/docs/FAQ.md +++ b/docs/FAQ.md @@ -22,7 +22,7 @@ The curl project produces two products: A client-side URL transfer library, supporting DICT, FILE, FTP, FTPS, GOPHER, GOPHERS, HTTP, HTTPS, IMAP, IMAPS, LDAP, LDAPS, MQTT, MQTTS, POP3, POP3S, -RTSP, SCP, SFTP, SMB, SMBS, SMTP, SMTPS, TELNET, TFTP, WS and WSS. +RTSP, SCP, SFTP, SMTP, SMTPS, TELNET, TFTP, WS and WSS. libcurl supports HTTPS certificates, HTTP POST, HTTP PUT, FTP uploading, Kerberos, SPNEGO, HTTP form based upload, proxies, cookies, user+password diff --git a/docs/FEATURES.md b/docs/FEATURES.md index a7f184505629..463aef6f17f5 100644 --- a/docs/FEATURES.md +++ b/docs/FEATURES.md @@ -184,13 +184,6 @@ SPDX-License-Identifier: curl - upload - resume -## SMB - -- SMBv1 over TCP and SSL -- download -- upload -- authentication with NTLMv1 - ## SMTP - authentication: Plain, Login, CRAM-MD5, Digest-MD5, NTLM, Kerberos 5 and diff --git a/docs/HISTORY.md b/docs/HISTORY.md index 479d9116d523..7711c3cd1ddc 100644 --- a/docs/HISTORY.md +++ b/docs/HISTORY.md @@ -503,3 +503,5 @@ December 15: the website served 78 TB over the last month. ## 2026 April: removed support for RTMP + +September: removed support for SMB diff --git a/docs/INSTALL-CMAKE.md b/docs/INSTALL-CMAKE.md index a16911ac13b1..951a38459e52 100644 --- a/docs/INSTALL-CMAKE.md +++ b/docs/INSTALL-CMAKE.md @@ -246,7 +246,6 @@ target_link_libraries(my_target PRIVATE CURL::libcurl) - `CURL_ENABLE_APPLE_FAST_UDP`: Use Apple fast UDP (experimental). Default: `OFF` - `CURL_ENABLE_NTLM`: Enable NTLM support. Default: `OFF` -- `CURL_ENABLE_SMB`: Enable SMB. Default: `OFF` - `CURL_ENABLE_SSL`: Enable SSL support. Default: `ON` - `CURL_WINDOWS_SSPI`: Enable SSPI on Windows. Default: =`CURL_USE_SCHANNEL` - `ENABLE_IPV6`: Enable IPv6 support. Default: `ON` if target supports IPv6. diff --git a/docs/MANUAL.md b/docs/MANUAL.md index 6f1cc2e90051..329fb5491e66 100644 --- a/docs/MANUAL.md +++ b/docs/MANUAL.md @@ -63,10 +63,6 @@ Get the main page from an IPv6 web server: curl "http://[2001:1890:1112:1::20]/" -Get a file from an SMB server: - - curl -u "domain\username:passwd" smb://server.example.com/share/file.txt - ## Download to a File Get a webpage and store in a local file with a specific name: @@ -244,11 +240,6 @@ fashion similar to: curl --proxytunnel -x proxy:port -T localfile ftp.example.com -### SMB / SMBS - - curl -T file.txt -u "domain\username:passwd" - smb://server.example.com/share/ - ### HTTP Upload all data on stdin to a specified HTTPS site: diff --git a/docs/TODO.md b/docs/TODO.md index 28f804eb41d6..05702598f7ff 100644 --- a/docs/TODO.md +++ b/docs/TODO.md @@ -930,13 +930,6 @@ http operations (for which we have test servers). Make the test suite work on more platforms. OpenBSD and macOS. Remove fork()s and it should become even more portable. -## write an SMB test server to replace impacket - -This would allow us to run SMB tests on more platforms and do better and more -covering tests. - -See [curl issue 15697](https://github.com/curl/curl/issues/15697) - ## Use the RFC 6265 test suite A test suite made for HTTP cookies (RFC 6265) by Adam Barth [is diff --git a/docs/URL-SYNTAX.md b/docs/URL-SYNTAX.md index 219e84ee932c..6e6fed4d5aa6 100644 --- a/docs/URL-SYNTAX.md +++ b/docs/URL-SYNTAX.md @@ -141,8 +141,8 @@ curl supports the following schemes on URLs specified to transfer. They are matched case insensitively: `dict`, `file`, `ftp`, `ftps`, `gopher`, `gophers`, `http`, `https`, `imap`, -`imaps`, `ldap`, `ldaps`, `mqtt`, `pop3`, `pop3s`, `rtsp`, `smb`, `smbs`, -`smtp`, `smtps`, `telnet`, `tftp` +`imaps`, `ldap`, `ldaps`, `mqtt`, `pop3`, `pop3s`, `rtsp`, `smtp`, `smtps`, +`telnet`, `tftp` When the URL is specified to identify a proxy, curl recognizes the following schemes: @@ -243,7 +243,7 @@ number based on the provide scheme: DICT 2628, FTP 21, FTPS 990, GOPHER 70, GOPHERS 70, HTTP 80, HTTPS 443, IMAP 143, IMAPS 993, LDAP 389, LDAPS 636, MQTT 1883, POP3 110, POP3S 995, RTSP 554, -SCP 22, SFTP 22, SMB 445, SMBS 445, SMTP 25, SMTPS 465, TELNET 23, TFTP 69 +SCP 22, SFTP 22, SMTP 25, SMTPS 465, TELNET 23, TFTP 69 ## Path @@ -398,17 +398,6 @@ path ends with a slash (`/`) then a directory listing is returned instead of a file. If the path is omitted entirely then the directory listing for the root / home directory is returned. -## SMB -The path part of an SMB request specifies the file to retrieve and from what -share and directory or the share to upload to and as such, may not be omitted. -If the username is embedded in the URL then it must contain the domain name -and as such, the backslash must be URL encoded as %2f. - -When uploading to SMB, the size of the file needs to be known ahead of time, -meaning that you can upload a file passed to curl over a pipe like stdin. - -curl supports SMB version 1 (only) - ## SMTP The path part of an SMTP request specifies the hostname to present during diff --git a/docs/cmdline-opts/_DESCRIPTION.md b/docs/cmdline-opts/_DESCRIPTION.md index bb21f0ba325a..ec94e1c9465e 100644 --- a/docs/cmdline-opts/_DESCRIPTION.md +++ b/docs/cmdline-opts/_DESCRIPTION.md @@ -4,8 +4,8 @@ **curl** is a tool for transferring data from or to a server using URLs. It supports these protocols: DICT, FILE, FTP, FTPS, GOPHER, GOPHERS, HTTP, HTTPS, -IMAP, IMAPS, LDAP, LDAPS, MQTT, MQTTS, POP3, POP3S, RTSP, SCP, SFTP, SMB, -SMBS, SMTP, SMTPS, TELNET, TFTP, WS and WSS. +IMAP, IMAPS, LDAP, LDAPS, MQTT, MQTTS, POP3, POP3S, RTSP, SCP, SFTP, SMTP, +SMTPS, TELNET, TFTP, WS and WSS. curl is powered by libcurl for all transfer-related features. See *libcurl(3)* for details. diff --git a/docs/cmdline-opts/_PROTOCOLS.md b/docs/cmdline-opts/_PROTOCOLS.md index 6b1918a9b227..b380c2d55bc8 100644 --- a/docs/cmdline-opts/_PROTOCOLS.md +++ b/docs/cmdline-opts/_PROTOCOLS.md @@ -36,8 +36,6 @@ curl supports RTSP 1.0 downloads. curl supports SSH version 2 scp transfers. ## SFTP curl supports SFTP (draft 5) done over SSH version 2. -## SMB(S) -curl supports SMB version 1 for upload and download. ## SMTP(S) Uploading contents to an SMTP server means sending an email. With or without TLS. diff --git a/docs/libcurl/opts/CURLINFO_PROTOCOL.md b/docs/libcurl/opts/CURLINFO_PROTOCOL.md index 3f9346fa049b..12a82c4da762 100644 --- a/docs/libcurl/opts/CURLINFO_PROTOCOL.md +++ b/docs/libcurl/opts/CURLINFO_PROTOCOL.md @@ -40,8 +40,8 @@ CURLPROTO_GOPHER, CURLPROTO_HTTP, CURLPROTO_HTTPS, CURLPROTO_IMAP, CURLPROTO_IMAPS, CURLPROTO_LDAP, CURLPROTO_LDAPS, CURLPROTO_POP3, CURLPROTO_POP3S, CURLPROTO_RTMP, CURLPROTO_RTMPE, CURLPROTO_RTMPS, CURLPROTO_RTMPT, CURLPROTO_RTMPTE, CURLPROTO_RTMPTS, CURLPROTO_RTSP, -CURLPROTO_SCP, CURLPROTO_SFTP, CURLPROTO_SMB, CURLPROTO_SMBS, CURLPROTO_SMTP, -CURLPROTO_SMTPS, CURLPROTO_TELNET, CURLPROTO_TFTP, CURLPROTO_MQTT +CURLPROTO_SCP, CURLPROTO_SFTP, CURLPROTO_SMTP, CURLPROTO_SMTPS, +CURLPROTO_TELNET, CURLPROTO_TFTP, CURLPROTO_MQTT ~~~ # %PROTOCOLS% diff --git a/docs/libcurl/opts/CURLOPT_DEFAULT_PROTOCOL.md b/docs/libcurl/opts/CURLOPT_DEFAULT_PROTOCOL.md index 1baf35ca0579..92930c07e47e 100644 --- a/docs/libcurl/opts/CURLOPT_DEFAULT_PROTOCOL.md +++ b/docs/libcurl/opts/CURLOPT_DEFAULT_PROTOCOL.md @@ -35,7 +35,7 @@ name. Use one of these protocol (scheme) names: dict, file, ftp, ftps, gopher, http, https, imap, imaps, ldap, ldaps, pop3, -pop3s, rtsp, scp, sftp, smb, smbs, smtp, smtps, telnet, tftp +pop3s, rtsp, scp, sftp, smtp, smtps, telnet, tftp An unknown or unsupported protocol causes error *CURLE_UNSUPPORTED_PROTOCOL* when libcurl parses a URL without a scheme. Parsing happens when diff --git a/docs/libcurl/opts/CURLOPT_FILETIME.md b/docs/libcurl/opts/CURLOPT_FILETIME.md index ccf93af2e0b0..e604e8ee14b2 100644 --- a/docs/libcurl/opts/CURLOPT_FILETIME.md +++ b/docs/libcurl/opts/CURLOPT_FILETIME.md @@ -12,7 +12,6 @@ Protocol: - FTP - SFTP - FILE - - SMB Added-in: 7.5 --- diff --git a/docs/libcurl/opts/CURLOPT_PROTOCOLS.md b/docs/libcurl/opts/CURLOPT_PROTOCOLS.md index 023773315998..a7d40b2f0ebb 100644 --- a/docs/libcurl/opts/CURLOPT_PROTOCOLS.md +++ b/docs/libcurl/opts/CURLOPT_PROTOCOLS.md @@ -56,17 +56,9 @@ CURLPROTO_LDAPS CURLPROTO_MQTT CURLPROTO_POP3 CURLPROTO_POP3S -CURLPROTO_RTMP -CURLPROTO_RTMPE -CURLPROTO_RTMPS -CURLPROTO_RTMPT -CURLPROTO_RTMPTE -CURLPROTO_RTMPTS CURLPROTO_RTSP CURLPROTO_SCP CURLPROTO_SFTP -CURLPROTO_SMB -CURLPROTO_SMBS CURLPROTO_SMTP CURLPROTO_SMTPS CURLPROTO_TELNET diff --git a/docs/libcurl/opts/CURLOPT_PROTOCOLS_STR.md b/docs/libcurl/opts/CURLOPT_PROTOCOLS_STR.md index 969bf88ab7dd..6986b8566733 100644 --- a/docs/libcurl/opts/CURLOPT_PROTOCOLS_STR.md +++ b/docs/libcurl/opts/CURLOPT_PROTOCOLS_STR.md @@ -42,8 +42,7 @@ set, it returns error. These are the available protocols: DICT, FILE, FTP, FTPS, GOPHER, GOPHERS, HTTP, HTTPS, IMAP, IMAPS, LDAP, LDAPS, -MQTT, MQTTS, POP3, POP3S, RTSP, SCP, SFTP, SMB, SMBS, SMTP, SMTPS, TELNET, -TFTP, WS, WSS +MQTT, MQTTS, POP3, POP3S, RTSP, SCP, SFTP, SMTP, SMTPS, TELNET, TFTP, WS, WSS You can set "ALL" as a short-cut to enable all protocols. Note that by setting all, you may enable protocols that were not supported the day you write this @@ -87,6 +86,8 @@ int main(int argc, char *argv[]) RTMP and its related protocol schemes are not supported since curl 8.20.0 +SMB and SMBS were removed in curl 8.23.0 + # %AVAILABILITY% # RETURN VALUE diff --git a/docs/libcurl/opts/CURLOPT_REDIR_PROTOCOLS.md b/docs/libcurl/opts/CURLOPT_REDIR_PROTOCOLS.md index 0803d587865d..0e9098d76303 100644 --- a/docs/libcurl/opts/CURLOPT_REDIR_PROTOCOLS.md +++ b/docs/libcurl/opts/CURLOPT_REDIR_PROTOCOLS.md @@ -61,17 +61,9 @@ CURLPROTO_LDAPS CURLPROTO_MQTT CURLPROTO_POP3 CURLPROTO_POP3S -CURLPROTO_RTMP -CURLPROTO_RTMPE -CURLPROTO_RTMPS -CURLPROTO_RTMPT -CURLPROTO_RTMPTE -CURLPROTO_RTMPTS CURLPROTO_RTSP CURLPROTO_SCP CURLPROTO_SFTP -CURLPROTO_SMB -CURLPROTO_SMBS CURLPROTO_SMTP CURLPROTO_SMTPS CURLPROTO_TELNET diff --git a/docs/libcurl/opts/CURLOPT_REDIR_PROTOCOLS_STR.md b/docs/libcurl/opts/CURLOPT_REDIR_PROTOCOLS_STR.md index 731b506319d9..d262a22b2f95 100644 --- a/docs/libcurl/opts/CURLOPT_REDIR_PROTOCOLS_STR.md +++ b/docs/libcurl/opts/CURLOPT_REDIR_PROTOCOLS_STR.md @@ -46,8 +46,7 @@ By default libcurl allows HTTP, HTTPS, FTP and FTPS on redirects (since These are the available protocols: DICT, FILE, FTP, FTPS, GOPHER, GOPHERS, HTTP, HTTPS, IMAP, IMAPS, LDAP, LDAPS, -MQTT, MQTTS, POP3, POP3S, RTSP, SCP, SFTP, SMB, SMBS, SMTP, SMTPS, TELNET, -TFTP, WS, WSS +MQTT, MQTTS, POP3, POP3S, RTSP, SCP, SFTP, SMTP, SMTPS, TELNET, TFTP, WS, WSS You can set "ALL" as a short-cut to enable all protocols. Note that by setting all, you may enable protocols that were not supported the day you write this @@ -89,6 +88,12 @@ int main(int argc, char *argv[]) } ~~~ +# HISTORY + +RTMP and its related protocol schemes are not supported since curl 8.20.0 + +SMB and SMBS were removed in curl 8.23.0 + # %AVAILABILITY% # RETURN VALUE diff --git a/docs/libcurl/symbols-in-versions b/docs/libcurl/symbols-in-versions index 6ac6fa5b3499..73449bdd2b47 100644 --- a/docs/libcurl/symbols-in-versions +++ b/docs/libcurl/symbols-in-versions @@ -992,8 +992,8 @@ CURLPROTO_RTMPTS 7.21.0 8.20.0 CURLPROTO_RTSP 7.20.0 CURLPROTO_SCP 7.19.4 CURLPROTO_SFTP 7.19.4 -CURLPROTO_SMB 7.40.0 -CURLPROTO_SMBS 7.40.0 +CURLPROTO_SMB 7.40.0 8.23.0 +CURLPROTO_SMBS 7.40.0 8.23.0 CURLPROTO_SMTP 7.20.0 CURLPROTO_SMTPS 7.20.0 CURLPROTO_TELNET 7.19.4 diff --git a/docs/tests/FILEFORMAT.md b/docs/tests/FILEFORMAT.md index cb4743b89153..54b189f1db86 100644 --- a/docs/tests/FILEFORMAT.md +++ b/docs/tests/FILEFORMAT.md @@ -193,8 +193,6 @@ Available substitute variables include: - `%RESOLVE` - server/resolve command - `%RTSP6PORT` - IPv6 port number of the RTSP server - `%RTSPPORT` - Port number of the RTSP server -- `%SMBPORT` - Port number of the SMB server -- `%SMBSPORT` - Port number of the SMBS server - `%SMTP6PORT` - IPv6 port number of the SMTP server - `%SMTPPORT` - Port number of the SMTP server - `%SOCKSPORT` - Port number of the SOCKS4/5 server @@ -453,7 +451,6 @@ What server(s) this test case requires/uses. Available servers: - `rtsp-ipv6` - `scp` - `sftp` -- `smb` - `smtp` - `socks4` - `socks5` diff --git a/docs/tests/TEST-SUITE.md b/docs/tests/TEST-SUITE.md index 6856eb5f8a21..465738952973 100644 --- a/docs/tests/TEST-SUITE.md +++ b/docs/tests/TEST-SUITE.md @@ -46,8 +46,7 @@ output from the servers used in the test. ## Requires to run - `perl` (and a Unix-style shell) -- `python` (and a Unix-style shell, for SMB and TELNET tests) -- `python-impacket` (for SMB tests) +- `python` (and a Unix-style shell, for TELNET tests) - `diff` (when a test fails, a diff is shown) - `stunnel` (for HTTPS and FTPS tests) - `openssl` (the command line tool, for generating test server certificates) diff --git a/lib/Makefile.inc b/lib/Makefile.inc index 330ae929b573..a36cc8611aa3 100644 --- a/lib/Makefile.inc +++ b/lib/Makefile.inc @@ -274,7 +274,6 @@ LIB_CFILES = \ setopt.c \ sha256.c \ slist.c \ - smb.c \ smtp.c \ socketpair.c \ socks.c \ @@ -409,7 +408,6 @@ LIB_HFILES = \ setup-win32.h \ sigpipe.h \ slist.h \ - smb.h \ smtp.h \ sockaddr.h \ socketpair.h \ diff --git a/lib/curl_config-cmake.h.in b/lib/curl_config-cmake.h.in index b316c0fb39ad..9ba3b42ec409 100644 --- a/lib/curl_config-cmake.h.in +++ b/lib/curl_config-cmake.h.in @@ -148,9 +148,6 @@ /* disabled shuffle DNS feature */ #cmakedefine CURL_DISABLE_SHUFFLE_DNS 1 -/* disables SMB */ -#cmakedefine CURL_ENABLE_SMB 1 - /* disables SMTP */ #cmakedefine CURL_DISABLE_SMTP 1 diff --git a/lib/protocol.c b/lib/protocol.c index 3bfcd88ebe46..a7b14601dee2 100644 --- a/lib/protocol.c +++ b/lib/protocol.c @@ -36,7 +36,6 @@ #include "mqtt.h" #include "pop3.h" #include "rtsp.h" -#include "smb.h" #include "smtp.h" #include "telnet.h" #include "tftp.h" @@ -308,32 +307,6 @@ const struct Curl_scheme Curl_scheme_scp = { PORT_SSH, /* defport */ }; -const struct Curl_scheme Curl_scheme_smb = { - "smb", /* scheme */ -#if defined(CURL_ENABLE_SMB) && defined(USE_CURL_NTLM_CORE) - &Curl_protocol_smb, -#else - ZERO_NULL, -#endif - CURLPROTO_SMB, /* protocol */ - CURLPROTO_SMB, /* family */ - PROTOPT_NONE, /* flags */ - PORT_SMB, /* defport */ -}; - -const struct Curl_scheme Curl_scheme_smbs = { - "smbs", /* scheme */ -#if defined(CURL_ENABLE_SMB) && defined(USE_CURL_NTLM_CORE) && defined(USE_SSL) - &Curl_protocol_smb, -#else - ZERO_NULL, -#endif - CURLPROTO_SMBS, /* protocol */ - CURLPROTO_SMB, /* family */ - PROTOPT_SSL, /* flags */ - PORT_SMBS, /* defport */ -}; - const struct Curl_scheme Curl_scheme_smtp = { "smtp", /* scheme */ #ifdef CURL_DISABLE_SMTP @@ -486,8 +459,6 @@ static const struct Curl_scheme *three_letter_scheme(const char *scheme) else if(s0 == 's') { if(s1 == 'c' && s2 == 'p') return &Curl_scheme_scp; - if(s1 == 'm' && s2 == 'b') - return &Curl_scheme_smb; } return NULL; } @@ -544,10 +515,6 @@ static const struct Curl_scheme *four_letter_scheme(const char *scheme) if(s1 == 'i' && s2 == 'c' && s3 == 't') return &Curl_scheme_dict; } - else if(s0 == 's') { - if(s1 == 'm' && s2 == 'b' && s3 == 's') - return &Curl_scheme_smbs; - } return NULL; } diff --git a/lib/protocol.h b/lib/protocol.h index 031bb834e417..a11166f09a5f 100644 --- a/lib/protocol.h +++ b/lib/protocol.h @@ -45,8 +45,6 @@ struct easy_pollset; #define PORT_IMAPS 993 #define PORT_POP3 110 #define PORT_POP3S 995 -#define PORT_SMB 445 -#define PORT_SMBS 445 #define PORT_SMTP 25 #define PORT_SMTPS 465 /* sometimes called SSMTP */ #define PORT_RTSP 554 @@ -85,7 +83,6 @@ typedef curl_off_t curl_prot_t; CURLPROTO_WSS) #define PROTO_FAMILY_FTP (CURLPROTO_FTP | CURLPROTO_FTPS) #define PROTO_FAMILY_POP3 (CURLPROTO_POP3 | CURLPROTO_POP3S) -#define PROTO_FAMILY_SMB (CURLPROTO_SMB | CURLPROTO_SMBS) #define PROTO_FAMILY_SMTP (CURLPROTO_SMTP | CURLPROTO_SMTPS) #define PROTO_FAMILY_SSH (CURLPROTO_SCP | CURLPROTO_SFTP) @@ -278,8 +275,6 @@ extern const struct Curl_scheme Curl_scheme_pop3s; extern const struct Curl_scheme Curl_scheme_rtsp; extern const struct Curl_scheme Curl_scheme_scp; extern const struct Curl_scheme Curl_scheme_sftp; -extern const struct Curl_scheme Curl_scheme_smb; -extern const struct Curl_scheme Curl_scheme_smbs; extern const struct Curl_scheme Curl_scheme_smtp; extern const struct Curl_scheme Curl_scheme_smtps; extern const struct Curl_scheme Curl_scheme_socks; diff --git a/lib/smb.c b/lib/smb.c deleted file mode 100644 index b82a99bdb10b..000000000000 --- a/lib/smb.c +++ /dev/null @@ -1,1243 +0,0 @@ -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) Daniel Stenberg, , et al. - * Copyright (C) Bill Nagel , Exacq Technologies - * - * This software is licensed as described in the file COPYING, which - * you should have received as part of this distribution. The terms - * are also available at https://curl.se/docs/copyright.html. - * - * You may opt to use, copy, modify, merge, publish, distribute and/or sell - * copies of the Software, and permit persons to whom the Software is - * furnished to do so, under the terms of the COPYING file. - * - * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY - * KIND, either express or implied. - * - * SPDX-License-Identifier: curl - * - ***************************************************************************/ -#include "curl_setup.h" -#include "urldata.h" - -#if defined(CURL_ENABLE_SMB) && defined(USE_CURL_NTLM_CORE) - -#ifdef HAVE_ARPA_INET_H -#include /* for htons() */ -#endif - -#include "smb.h" -#include "url.h" -#include "sendf.h" -#include "curl_trc.h" -#include "cfilters.h" -#include "connect.h" -#include "progress.h" -#include "transfer.h" -#include "select.h" -#include "curl_ntlm_core.h" -#include "escape.h" -#include "curl_endian.h" -#include "curlx/strcopy.h" - -/* meta key for storing protocol meta at easy handle */ -#define CURL_META_SMB_EASY "meta:proto:smb:easy" -/* meta key for storing protocol meta at connection */ -#define CURL_META_SMB_CONN "meta:proto:smb:conn" - -enum smb_conn_state { - SMB_NOT_CONNECTED = 0, - SMB_CONNECTING, - SMB_NEGOTIATE, - SMB_SETUP, - SMB_CONNECTED -}; - -/* SMB connection data, kept at connection */ -struct smb_conn { - enum smb_conn_state state; - const char *user; - char *domain; - char *share; - unsigned char challenge[8]; - unsigned int session_key; - unsigned short uid; - char *recv_buf; - char *send_buf; - size_t upload_size; - size_t send_size; - size_t sent; - size_t got; -}; - -/* SMB request state */ -enum smb_req_state { - SMB_REQUESTING, - SMB_TREE_CONNECT, - SMB_OPEN, - SMB_DOWNLOAD, - SMB_UPLOAD, - SMB_CLOSE, - SMB_TREE_DISCONNECT, - SMB_DONE -}; - -/* SMB request data, kept at easy handle */ -struct smb_request { - enum smb_req_state state; - char *path; - unsigned short tid; /* Even if we connect to the same tree as another */ - unsigned short fid; /* request, the tid will be different */ - CURLcode result; -}; - -/* - * Definitions for SMB protocol data structures - */ -#if defined(_MSC_VER) || defined(__ILEC400__) -# define PACK -# pragma pack(push) -# pragma pack(1) -#elif defined(__GNUC__) -# define PACK __attribute__((packed)) -#else -# define PACK -#endif - -#define SMB_COM_CLOSE 0x04 -#define SMB_COM_READ_ANDX 0x2e -#define SMB_COM_WRITE_ANDX 0x2f -#define SMB_COM_TREE_DISCONNECT 0x71 -#define SMB_COM_NEGOTIATE 0x72 -#define SMB_COM_SETUP_ANDX 0x73 -#define SMB_COM_TREE_CONNECT_ANDX 0x75 -#define SMB_COM_NT_CREATE_ANDX 0xa2 -#define SMB_COM_NO_ANDX_COMMAND 0xff - -#define SMB_WC_CLOSE 0x03 -#define SMB_WC_READ_ANDX 0x0c -#define SMB_WC_WRITE_ANDX 0x0e -#define SMB_WC_SETUP_ANDX 0x0d -#define SMB_WC_TREE_CONNECT_ANDX 0x04 -#define SMB_WC_NT_CREATE_ANDX 0x18 - -#define SMB_FLAGS_CANONICAL_PATHNAMES 0x10 -#define SMB_FLAGS_CASELESS_PATHNAMES 0x08 -/* #define SMB_FLAGS2_UNICODE_STRINGS 0x8000 */ -#define SMB_FLAGS2_IS_LONG_NAME 0x0040 -#define SMB_FLAGS2_KNOWS_LONG_NAME 0x0001 - -#define SMB_CAP_LARGE_FILES 0x08 -#define SMB_GENERIC_WRITE 0x40000000 -#define SMB_GENERIC_READ 0x80000000 -#define SMB_FILE_SHARE_ALL 0x07 -#define SMB_FILE_OPEN 0x01 -#define SMB_FILE_OVERWRITE_IF 0x05 - -#define SMB_ERR_NOACCESS 0x00050001 - -struct smb_header { - unsigned char nbt_type; - unsigned char nbt_flags; - unsigned short nbt_length; - unsigned char magic[4]; - unsigned char command; - unsigned int status; - unsigned char flags; - unsigned short flags2; - unsigned short pid_high; - unsigned char signature[8]; - unsigned short pad; - unsigned short tid; - unsigned short pid; - unsigned short uid; - unsigned short mid; -} PACK; - -struct smb_negotiate_response { - struct smb_header h; - unsigned char word_count; - unsigned short dialect_index; - unsigned char security_mode; - unsigned short max_mpx_count; - unsigned short max_number_vcs; - unsigned int max_buffer_size; - unsigned int max_raw_size; - unsigned int session_key; - unsigned int capabilities; - unsigned int system_time_low; - unsigned int system_time_high; - unsigned short server_time_zone; - unsigned char encryption_key_length; - unsigned short byte_count; - char bytes[1]; -} PACK; - -struct andx { - unsigned char command; - unsigned char pad; - unsigned short offset; -} PACK; - -struct smb_setup { - unsigned char word_count; - struct andx andx; - unsigned short max_buffer_size; - unsigned short max_mpx_count; - unsigned short vc_number; - unsigned int session_key; - unsigned short lengths[2]; - unsigned int pad; - unsigned int capabilities; - unsigned short byte_count; - char bytes[1024]; -} PACK; - -struct smb_tree_connect { - unsigned char word_count; - struct andx andx; - unsigned short flags; - unsigned short pw_len; - unsigned short byte_count; - char bytes[1024]; -} PACK; - -struct smb_nt_create { - unsigned char word_count; - struct andx andx; - unsigned char pad; - unsigned short name_length; - unsigned int flags; - unsigned int root_fid; - unsigned int access; - curl_off_t allocation_size; - unsigned int ext_file_attributes; - unsigned int share_access; - unsigned int create_disposition; - unsigned int create_options; - unsigned int impersonation_level; - unsigned char security_flags; - unsigned short byte_count; - char bytes[1024]; -} PACK; - -struct smb_nt_create_response { - struct smb_header h; - unsigned char word_count; - struct andx andx; - unsigned char op_lock_level; - unsigned short fid; - unsigned int create_disposition; - - curl_off_t create_time; - curl_off_t last_access_time; - curl_off_t last_write_time; - curl_off_t last_change_time; - unsigned int ext_file_attributes; - curl_off_t allocation_size; - curl_off_t end_of_file; -} PACK; - -struct smb_read { - unsigned char word_count; - struct andx andx; - unsigned short fid; - unsigned int offset; - unsigned short max_bytes; - unsigned short min_bytes; - unsigned int timeout; - unsigned short remaining; - unsigned int offset_high; - unsigned short byte_count; -} PACK; - -struct smb_write { - struct smb_header h; - unsigned char word_count; - struct andx andx; - unsigned short fid; - unsigned int offset; - unsigned int timeout; - unsigned short write_mode; - unsigned short remaining; - unsigned short pad; - unsigned short data_length; - unsigned short data_offset; - unsigned int offset_high; - unsigned short byte_count; - unsigned char pad2; -} PACK; - -struct smb_close { - unsigned char word_count; - unsigned short fid; - unsigned int last_mtime; - unsigned short byte_count; -} PACK; - -struct smb_tree_disconnect { - unsigned char word_count; - unsigned short byte_count; -} PACK; - -#if defined(_MSC_VER) || defined(__ILEC400__) -# pragma pack(pop) -#endif - -#define MAX_PAYLOAD_SIZE 0x8000 -#define MAX_MESSAGE_SIZE (MAX_PAYLOAD_SIZE + 0x1000) -#define CLIENTNAME "curl" -#define SERVICENAME "?????" - -/* SMB is mostly little endian */ -#if (defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__) || \ - defined(__OS400__) -static unsigned short smb_swap16(unsigned short x) -{ - return (unsigned short)((x << 8) | ((x >> 8) & 0xff)); -} - -static unsigned int smb_swap32(unsigned int x) -{ - return (x << 24) | ((x << 8) & 0xff0000) | ((x >> 8) & 0xff00) | - ((x >> 24) & 0xff); -} - -static curl_off_t smb_swap64(curl_off_t x) -{ - return ((curl_off_t)smb_swap32((unsigned int)x) << 32) | - smb_swap32((unsigned int)(x >> 32)); -} - -#else -# define smb_swap16(x) (x) -# define smb_swap32(x) (x) -# define smb_swap64(x) (x) -#endif - -static void conn_state(struct Curl_easy *data, struct smb_conn *smbc, - enum smb_conn_state newstate) -{ -#if defined(DEBUGBUILD) && defined(CURLVERBOSE) - /* For debug purposes */ - static const char * const names[] = { - "SMB_NOT_CONNECTED", - "SMB_CONNECTING", - "SMB_NEGOTIATE", - "SMB_SETUP", - "SMB_CONNECTED", - /* LAST */ - }; - - if(smbc->state != newstate) - infof(data, "SMB conn %p state change from %s to %s", - (void *)smbc, names[smbc->state], names[newstate]); -#else - (void)data; -#endif - smbc->state = newstate; -} - -static void request_state(struct Curl_easy *data, - enum smb_req_state newstate) -{ - struct smb_request *req = Curl_meta_get(data, CURL_META_SMB_EASY); - if(req) { -#if defined(DEBUGBUILD) && defined(CURLVERBOSE) - /* For debug purposes */ - static const char * const names[] = { - "SMB_REQUESTING", - "SMB_TREE_CONNECT", - "SMB_OPEN", - "SMB_DOWNLOAD", - "SMB_UPLOAD", - "SMB_CLOSE", - "SMB_TREE_DISCONNECT", - "SMB_DONE", - /* LAST */ - }; - - if(req->state != newstate) - infof(data, "SMB request %p state change from %s to %s", - (void *)req, names[req->state], names[newstate]); -#endif - - req->state = newstate; - } -} - -static void smb_easy_dtor(const void *key, size_t klen, void *entry) -{ - struct smb_request *req = entry; - (void)key; - (void)klen; - curlx_safefree(req->path); - curlx_free(req); -} - -static void smb_conn_dtor(const void *key, size_t klen, void *entry) -{ - struct smb_conn *smbc = entry; - (void)key; - (void)klen; - curlx_safefree(smbc->share); - curlx_safefree(smbc->domain); - curlx_safefree(smbc->recv_buf); - curlx_safefree(smbc->send_buf); - curlx_free(smbc); -} - -static CURLcode smb_parse_url_path(struct Curl_easy *data, - struct smb_conn *smbc, - struct smb_request *req) -{ - char *path; - char *slash, *s; - CURLcode result; - - /* URL decode the path */ - result = Curl_urldecode(data->state.up.path, 0, &path, NULL, REJECT_CTRL); - if(result) - return result; - - /* Parse the path for the share */ - curlx_safefree(smbc->share); - smbc->share = curlx_strdup((*path == '/' || *path == '\\') - ? path + 1 : path); - curlx_free(path); - if(!smbc->share) - return CURLE_OUT_OF_MEMORY; - - slash = strchr(smbc->share, '/'); - if(!slash) - slash = strchr(smbc->share, '\\'); - - /* The share must be present */ - if(!slash) { - curlx_safefree(smbc->share); - failf(data, "missing share in URL path for SMB"); - return CURLE_URL_MALFORMAT; - } - - /* Parse the path for the file path converting any forward slashes into - backslashes */ - *slash++ = 0; - for(s = slash; *s; s++) { - if(*s == '/') - *s = '\\'; - } - /* keep a copy at easy struct to not share this with connection state */ - req->path = curlx_strdup(slash); - if(!req->path) - return CURLE_OUT_OF_MEMORY; - - return CURLE_OK; -} - -/* this should setup things in the connection, not in the easy - handle */ -static CURLcode smb_setup_connection(struct Curl_easy *data, - struct connectdata *conn) -{ - struct smb_conn *smbc; - struct smb_request *req; - - /* Initialize the connection state */ - smbc = curlx_calloc(1, sizeof(*smbc)); - if(!smbc || - Curl_conn_meta_set(conn, CURL_META_SMB_CONN, smbc, smb_conn_dtor)) - return CURLE_OUT_OF_MEMORY; - - /* Initialize the request state */ - req = curlx_calloc(1, sizeof(*req)); - if(!req || - Curl_meta_set(data, CURL_META_SMB_EASY, req, smb_easy_dtor)) - return CURLE_OUT_OF_MEMORY; - - /* Parse the URL path */ - return smb_parse_url_path(data, smbc, req); -} - -static CURLcode smb_connect(struct Curl_easy *data, bool *done) -{ - struct connectdata *conn = data->conn; - struct smb_conn *smbc = Curl_conn_meta_get(conn, CURL_META_SMB_CONN); - const char *slash; - const char *user = Curl_creds_user(conn->creds); - - (void)done; - if(!smbc) - return CURLE_FAILED_INIT; - - /* Check we have a username and password to authenticate with */ - if(!Curl_creds_has_user(data->state.creds)) - return CURLE_LOGIN_DENIED; - - /* Initialize the connection state */ - smbc->state = SMB_CONNECTING; - smbc->recv_buf = curlx_malloc(MAX_MESSAGE_SIZE); - if(!smbc->recv_buf) - return CURLE_OUT_OF_MEMORY; - smbc->send_buf = curlx_malloc(MAX_MESSAGE_SIZE); - if(!smbc->send_buf) - return CURLE_OUT_OF_MEMORY; - - /* Parse the username, domain, and password */ - slash = strchr(user, '/'); - if(!slash) - slash = strchr(user, '\\'); - - if(slash) { - smbc->user = slash + 1; - smbc->domain = curlx_strdup(user); - if(!smbc->domain) - return CURLE_OUT_OF_MEMORY; - smbc->domain[slash - user] = 0; - } - else { - smbc->user = user; - smbc->domain = curlx_strdup(conn->origin->hostname); - if(!smbc->domain) - return CURLE_OUT_OF_MEMORY; - } - - return CURLE_OK; -} - -static CURLcode smb_recv_message(struct Curl_easy *data, - struct smb_conn *smbc, - void **msg) -{ - char *buf = smbc->recv_buf; - size_t bytes_read; - size_t nbt_size; - size_t msg_size = sizeof(struct smb_header); - size_t len = MAX_MESSAGE_SIZE - smbc->got; - CURLcode result; - - result = Curl_xfer_recv(data, buf + smbc->got, len, &bytes_read); - if(result) - return result; - - if(!bytes_read) - return CURLE_OK; - - smbc->got += bytes_read; - - /* Check for a 32-bit nbt header */ - if(smbc->got < sizeof(unsigned int)) - return CURLE_OK; - - nbt_size = Curl_read16_be((const unsigned char *) - (buf + sizeof(unsigned short))) + - sizeof(unsigned int); - if(nbt_size > MAX_MESSAGE_SIZE) { - failf(data, "too large NetBIOS frame size %zu", nbt_size); - return CURLE_RECV_ERROR; - } - else if(nbt_size < msg_size) { - /* Each SMB message must be at least this large, e.g. 32 bytes */ - failf(data, "too small NetBIOS frame size %zu", nbt_size); - return CURLE_RECV_ERROR; - } - - if(smbc->got < nbt_size) - return CURLE_OK; - - if(nbt_size >= msg_size + 1) { - /* Add the word count */ - msg_size += 1 + (((unsigned char)buf[msg_size]) * sizeof(unsigned short)); - if(nbt_size >= msg_size + sizeof(unsigned short)) { - /* Add the byte count */ - msg_size += sizeof(unsigned short) + - Curl_read16_le((const unsigned char *)&buf[msg_size]); - if(nbt_size < msg_size) - return CURLE_RECV_ERROR; - } - } - - *msg = buf; - - return CURLE_OK; -} - -static void smb_pop_message(struct smb_conn *smbc) -{ - smbc->got = 0; -} - -static void smb_format_message(struct smb_conn *smbc, - struct smb_request *req, - struct smb_header *h, - unsigned char cmd, size_t len) -{ - const unsigned int pid = 0xbad71d; /* made up */ - - memset(h, 0, sizeof(*h)); - h->nbt_length = htons((unsigned short)(sizeof(*h) - sizeof(unsigned int) + - len)); - memcpy((char *)h->magic, "\xffSMB", 4); - h->command = cmd; - h->flags = SMB_FLAGS_CANONICAL_PATHNAMES | SMB_FLAGS_CASELESS_PATHNAMES; - h->flags2 = smb_swap16(SMB_FLAGS2_IS_LONG_NAME | SMB_FLAGS2_KNOWS_LONG_NAME); - h->uid = smb_swap16(smbc->uid); - h->tid = smb_swap16(req->tid); - h->pid_high = smb_swap16((unsigned short)(pid >> 16)); - h->pid = smb_swap16((unsigned short)pid); -} - -static CURLcode smb_send(struct Curl_easy *data, struct smb_conn *smbc, - size_t len, size_t upload_size) -{ - size_t bytes_written; - CURLcode result; - - result = Curl_xfer_send(data, smbc->send_buf, len, FALSE, &bytes_written); - if(result) - return result; - - if(bytes_written != len) { - smbc->send_size = len; - smbc->sent = bytes_written; - } - - smbc->upload_size = upload_size; - - return CURLE_OK; -} - -static CURLcode smb_flush(struct Curl_easy *data, struct smb_conn *smbc) -{ - size_t bytes_written; - size_t len = smbc->send_size - smbc->sent; - CURLcode result; - - if(!smbc->send_size) - return CURLE_OK; - - result = Curl_xfer_send(data, smbc->send_buf + smbc->sent, len, FALSE, - &bytes_written); - if(result) - return result; - - if(bytes_written != len) - smbc->sent += bytes_written; - else - smbc->send_size = 0; - - return CURLE_OK; -} - -static CURLcode smb_send_message(struct Curl_easy *data, - struct smb_conn *smbc, - struct smb_request *req, - unsigned char cmd, - const void *msg, size_t msg_len) -{ - if((MAX_MESSAGE_SIZE - sizeof(struct smb_header)) < msg_len) { - DEBUGASSERT(0); - return CURLE_SEND_ERROR; - } - smb_format_message(smbc, req, (struct smb_header *)smbc->send_buf, - cmd, msg_len); - memcpy(smbc->send_buf + sizeof(struct smb_header), msg, msg_len); - - return smb_send(data, smbc, sizeof(struct smb_header) + msg_len, 0); -} - -static CURLcode smb_send_negotiate(struct Curl_easy *data, - struct smb_conn *smbc, - struct smb_request *req) -{ - static const char msg[] = "\x00\x0c\x00\x02NT LM 0.12"; - - return smb_send_message(data, smbc, req, SMB_COM_NEGOTIATE, msg, - sizeof(msg)); -} - -static CURLcode smb_send_setup(struct Curl_easy *data) -{ - struct connectdata *conn = data->conn; - struct smb_conn *smbc = Curl_conn_meta_get(conn, CURL_META_SMB_CONN); - struct smb_request *req = Curl_meta_get(data, CURL_META_SMB_EASY); - struct smb_setup msg; - char *p = msg.bytes; - unsigned char lm_hash[21]; - unsigned char lm[24]; - unsigned char nt_hash[21]; - unsigned char nt[24]; - size_t byte_count; - const char *passwd = Curl_creds_passwd(conn->creds); - - if(!smbc || !req) - return CURLE_FAILED_INIT; - - byte_count = sizeof(lm) + sizeof(nt) + - strlen(smbc->user) + strlen(smbc->domain) + - CURL_CSTRLEN(CURL_OS) + CURL_CSTRLEN(CLIENTNAME) + 4; /* 4 null chars */ - if(byte_count > sizeof(msg.bytes)) - return CURLE_FILESIZE_EXCEEDED; - - Curl_ntlm_core_mk_lm_hash(passwd, lm_hash); - Curl_ntlm_core_lm_resp(lm_hash, smbc->challenge, lm); - Curl_ntlm_core_mk_nt_hash(passwd, nt_hash); - Curl_ntlm_core_lm_resp(nt_hash, smbc->challenge, nt); - - memset(&msg, 0, sizeof(msg) - sizeof(msg.bytes)); - msg.word_count = SMB_WC_SETUP_ANDX; - msg.andx.command = SMB_COM_NO_ANDX_COMMAND; - msg.max_buffer_size = smb_swap16(MAX_MESSAGE_SIZE); - msg.max_mpx_count = smb_swap16(1); - msg.vc_number = smb_swap16(1); - msg.session_key = smb_swap32(smbc->session_key); - msg.capabilities = smb_swap32(SMB_CAP_LARGE_FILES); - msg.lengths[0] = smb_swap16(sizeof(lm)); - msg.lengths[1] = smb_swap16(sizeof(nt)); - memcpy(p, lm, sizeof(lm)); - p += sizeof(lm); - memcpy(p, nt, sizeof(nt)); - p += sizeof(nt); - p += curl_msnprintf(p, byte_count - sizeof(nt) - sizeof(lm), - "%s%c" /* user */ - "%s%c" /* domain */ - "%s%c" /* OS */ - "%s", /* client name */ - smbc->user, 0, smbc->domain, 0, CURL_OS, 0, CLIENTNAME); - p++; /* count the final null-termination */ - DEBUGASSERT(byte_count == (size_t)(p - msg.bytes)); - msg.byte_count = smb_swap16((unsigned short)byte_count); - - return smb_send_message(data, smbc, req, SMB_COM_SETUP_ANDX, &msg, - sizeof(msg) - sizeof(msg.bytes) + byte_count); -} - -static CURLcode smb_send_tree_connect(struct Curl_easy *data, - struct smb_conn *smbc, - struct smb_request *req) -{ - struct smb_tree_connect msg; - struct connectdata *conn = data->conn; - char *p = msg.bytes; - const size_t byte_count = strlen(conn->origin->hostname) + - strlen(smbc->share) + - CURL_CSTRLEN(SERVICENAME) + 5; /* 2 nulls and 3 backslashes */ - - if(byte_count > sizeof(msg.bytes)) - return CURLE_FILESIZE_EXCEEDED; - - memset(&msg, 0, sizeof(msg) - sizeof(msg.bytes)); - msg.word_count = SMB_WC_TREE_CONNECT_ANDX; - msg.andx.command = SMB_COM_NO_ANDX_COMMAND; - msg.pw_len = 0; - - p += curl_msnprintf(p, byte_count, - "\\\\%s\\" /* hostname */ - "%s%c" /* share */ - "%s", /* service */ - conn->origin->hostname, smbc->share, 0, SERVICENAME); - p++; /* count the final null-termination */ - DEBUGASSERT(byte_count == (size_t)(p - msg.bytes)); - msg.byte_count = smb_swap16((unsigned short)byte_count); - - return smb_send_message(data, smbc, req, SMB_COM_TREE_CONNECT_ANDX, &msg, - sizeof(msg) - sizeof(msg.bytes) + byte_count); -} - -static CURLcode smb_send_open(struct Curl_easy *data, - struct smb_conn *smbc, - struct smb_request *req) -{ - struct smb_nt_create msg; - const size_t byte_count = strlen(req->path) + 1; - - if(byte_count > sizeof(msg.bytes)) - return CURLE_FILESIZE_EXCEEDED; - - memset(&msg, 0, sizeof(msg) - sizeof(msg.bytes)); - msg.word_count = SMB_WC_NT_CREATE_ANDX; - msg.andx.command = SMB_COM_NO_ANDX_COMMAND; - msg.name_length = smb_swap16((unsigned short)(byte_count - 1)); - msg.share_access = smb_swap32(SMB_FILE_SHARE_ALL); - if(data->state.upload) { - msg.access = smb_swap32(SMB_GENERIC_READ | SMB_GENERIC_WRITE); - msg.create_disposition = smb_swap32(SMB_FILE_OVERWRITE_IF); - } - else { - msg.access = smb_swap32(SMB_GENERIC_READ); - msg.create_disposition = smb_swap32(SMB_FILE_OPEN); - } - msg.byte_count = smb_swap16((unsigned short)byte_count); - curlx_strcopy(msg.bytes, sizeof(msg.bytes), req->path, byte_count - 1); - - return smb_send_message(data, smbc, req, SMB_COM_NT_CREATE_ANDX, &msg, - sizeof(msg) - sizeof(msg.bytes) + byte_count); -} - -static CURLcode smb_send_close(struct Curl_easy *data, - struct smb_conn *smbc, - struct smb_request *req) -{ - struct smb_close msg; - - memset(&msg, 0, sizeof(msg)); - msg.word_count = SMB_WC_CLOSE; - msg.fid = smb_swap16(req->fid); - - return smb_send_message(data, smbc, req, SMB_COM_CLOSE, &msg, sizeof(msg)); -} - -static CURLcode smb_send_tree_disconnect(struct Curl_easy *data, - struct smb_conn *smbc, - struct smb_request *req) -{ - struct smb_tree_disconnect msg; - memset(&msg, 0, sizeof(msg)); - return smb_send_message(data, smbc, req, SMB_COM_TREE_DISCONNECT, - &msg, sizeof(msg)); -} - -static CURLcode smb_send_read(struct Curl_easy *data, - struct smb_conn *smbc, - struct smb_request *req) -{ - curl_off_t offset = data->req.offset; - struct smb_read msg; - - memset(&msg, 0, sizeof(msg)); - msg.word_count = SMB_WC_READ_ANDX; - msg.andx.command = SMB_COM_NO_ANDX_COMMAND; - msg.fid = smb_swap16(req->fid); - msg.offset = smb_swap32((unsigned int)offset); - msg.offset_high = smb_swap32((unsigned int)(offset >> 32)); - msg.min_bytes = smb_swap16(MAX_PAYLOAD_SIZE); - msg.max_bytes = smb_swap16(MAX_PAYLOAD_SIZE); - - return smb_send_message(data, smbc, req, SMB_COM_READ_ANDX, - &msg, sizeof(msg)); -} - -static CURLcode smb_send_write(struct Curl_easy *data, - struct smb_conn *smbc, - struct smb_request *req) -{ - struct smb_write *msg; - curl_off_t offset = data->req.offset; - curl_off_t upload_size = data->req.size - data->req.bytecount; - - msg = (struct smb_write *)smbc->send_buf; - if(upload_size >= MAX_PAYLOAD_SIZE - 1) /* There is one byte of padding */ - upload_size = MAX_PAYLOAD_SIZE - 1; - - memset(msg, 0, sizeof(*msg)); - msg->word_count = SMB_WC_WRITE_ANDX; - msg->andx.command = SMB_COM_NO_ANDX_COMMAND; - msg->fid = smb_swap16(req->fid); - msg->offset = smb_swap32((unsigned int)offset); - msg->offset_high = smb_swap32((unsigned int)(offset >> 32)); - msg->data_length = smb_swap16((unsigned short)upload_size); - msg->data_offset = smb_swap16(sizeof(*msg) - sizeof(unsigned int)); - msg->byte_count = smb_swap16((unsigned short)(upload_size + 1)); - - smb_format_message(smbc, req, &msg->h, SMB_COM_WRITE_ANDX, - sizeof(*msg) - sizeof(msg->h) + (size_t)upload_size); - - return smb_send(data, smbc, sizeof(*msg), (size_t)upload_size); -} - -static CURLcode smb_send_and_recv(struct Curl_easy *data, - struct smb_conn *smbc, void **msg) -{ - CURLcode result; - *msg = NULL; /* if it returns early */ - - /* Check if there is data in the transfer buffer */ - if(!smbc->send_size && smbc->upload_size) { - size_t nread = smbc->upload_size > (size_t)MAX_MESSAGE_SIZE ? - (size_t)MAX_MESSAGE_SIZE : smbc->upload_size; - bool eos; - - result = Curl_client_read(data, smbc->send_buf, nread, &nread, &eos); - if(result && result != CURLE_AGAIN) - return result; - if(!nread) - return CURLE_OK; - - smbc->upload_size -= nread; - smbc->send_size = nread; - smbc->sent = 0; - } - - /* Check if there is data to send */ - if(smbc->send_size) { - result = smb_flush(data, smbc); - if(result) - return result; - } - - /* Check if there is still data to be sent */ - if(smbc->send_size || smbc->upload_size) - return CURLE_AGAIN; - - return smb_recv_message(data, smbc, msg); -} - -static CURLcode smb_connection_state(struct Curl_easy *data, bool *done) -{ - struct connectdata *conn = data->conn; - struct smb_conn *smbc = Curl_conn_meta_get(conn, CURL_META_SMB_CONN); - struct smb_request *req = Curl_meta_get(data, CURL_META_SMB_EASY); - struct smb_negotiate_response *nrsp; - struct smb_header *h; - CURLcode result; - void *msg = NULL; - - if(!smbc || !req) - return CURLE_FAILED_INIT; - - if(smbc->state == SMB_CONNECTING) { -#ifdef USE_SSL - if(Curl_conn_is_ssl(conn, FIRSTSOCKET)) { - bool ssl_done = FALSE; - result = Curl_conn_connect(data, FIRSTSOCKET, FALSE, &ssl_done); - if(result && result != CURLE_AGAIN) - return result; - if(!ssl_done) - return CURLE_OK; - } -#endif - - result = smb_send_negotiate(data, smbc, req); - if(result) { - CURL_TRC_M(data, "SMB: failed to send negotiate message"); - connclose(conn); - return result; - } - - conn_state(data, smbc, SMB_NEGOTIATE); - } - - /* Send the previous message and check for a response */ - result = smb_send_and_recv(data, smbc, &msg); - if(result && result != CURLE_AGAIN) { - CURL_TRC_M(data, "SMB: failed to communicate"); - connclose(conn); - return result; - } - - if(!msg) - return CURLE_OK; - - h = msg; - - switch(smbc->state) { - case SMB_NEGOTIATE: - if((smbc->got < sizeof(*nrsp) + sizeof(smbc->challenge) - 1) || - h->status) { - CURL_TRC_M(data, "SMB: negotiation failed"); - connclose(conn); - return CURLE_COULDNT_CONNECT; - } - nrsp = msg; -#if defined(__GNUC__) && __GNUC__ >= 13 -#pragma GCC diagnostic push -/* error: 'memcpy' offset [74, 80] from the object at '' is out of - the bounds of referenced subobject 'bytes' with type 'char[1]' */ -#pragma GCC diagnostic ignored "-Warray-bounds" -#endif - memcpy(smbc->challenge, nrsp->bytes, sizeof(smbc->challenge)); -#if defined(__GNUC__) && __GNUC__ >= 13 -#pragma GCC diagnostic pop -#endif - smbc->session_key = smb_swap32(nrsp->session_key); - result = smb_send_setup(data); - if(result) { - CURL_TRC_M(data, "SMB: failed to send setup message"); - connclose(conn); - return result; - } - conn_state(data, smbc, SMB_SETUP); - break; - - case SMB_SETUP: - if(h->status) { - CURL_TRC_M(data, "SMB: authentication failed"); - connclose(conn); - return CURLE_LOGIN_DENIED; - } - smbc->uid = smb_swap16(h->uid); - conn_state(data, smbc, SMB_CONNECTED); - *done = TRUE; - break; - - default: - smb_pop_message(smbc); - return CURLE_OK; /* ignore */ - } - - smb_pop_message(smbc); - - return CURLE_OK; -} - -/* - * Convert a timestamp from the Windows world (100 nsec units from 1 Jan 1601) - * to POSIX time. Cap the output to fit within a time_t. - */ -static void get_posix_time(time_t *out, curl_off_t timestamp) -{ - if(timestamp >= (curl_off_t)116444736000000000) { - timestamp -= (curl_off_t)116444736000000000; - timestamp /= 10000000; -#if SIZEOF_TIME_T < SIZEOF_CURL_OFF_T - if(timestamp > TIME_T_MAX) - *out = TIME_T_MAX; - else if(timestamp < TIME_T_MIN) - *out = TIME_T_MIN; - else -#endif - *out = (time_t)timestamp; - } - else - *out = 0; -} - -static CURLcode smb_request_state(struct Curl_easy *data, bool *done) -{ - struct connectdata *conn = data->conn; - struct smb_conn *smbc = Curl_conn_meta_get(conn, CURL_META_SMB_CONN); - struct smb_request *req = Curl_meta_get(data, CURL_META_SMB_EASY); - struct smb_header *h; - enum smb_req_state next_state = SMB_DONE; - size_t len; - size_t off; - CURLcode result; - void *msg = NULL; - const struct smb_nt_create_response *smb_m; - - if(!smbc || !req) - return CURLE_FAILED_INIT; - - if(data->state.upload && (data->state.infilesize < 0)) { - failf(data, "SMB upload needs to know the size up front"); - return CURLE_SEND_ERROR; - } - - /* Start the request */ - if(req->state == SMB_REQUESTING) { - result = smb_send_tree_connect(data, smbc, req); - if(result) { - CURL_TRC_M(data, "SMB: failed to send tree connect message"); - connclose(conn); - return result; - } - - request_state(data, SMB_TREE_CONNECT); - } - - /* Send the previous message and check for a response */ - result = smb_send_and_recv(data, smbc, &msg); - if(result && result != CURLE_AGAIN) { - CURL_TRC_M(data, "SMB: failed to communicate"); - connclose(conn); - return result; - } - - if(!msg) - return CURLE_OK; - - h = msg; - - switch(req->state) { - case SMB_TREE_CONNECT: - if(h->status) { - req->result = CURLE_REMOTE_FILE_NOT_FOUND; - if(h->status == smb_swap32(SMB_ERR_NOACCESS)) - req->result = CURLE_REMOTE_ACCESS_DENIED; - break; - } - req->tid = smb_swap16(h->tid); - next_state = SMB_OPEN; - break; - - case SMB_OPEN: - if(h->status || smbc->got < sizeof(struct smb_nt_create_response)) { - req->result = CURLE_REMOTE_FILE_NOT_FOUND; - if(h->status == smb_swap32(SMB_ERR_NOACCESS)) - req->result = CURLE_REMOTE_ACCESS_DENIED; - next_state = SMB_TREE_DISCONNECT; - break; - } - smb_m = (const struct smb_nt_create_response *)msg; - req->fid = smb_swap16(smb_m->fid); - data->req.offset = 0; - if(data->state.upload) { - data->req.size = data->state.infilesize; - Curl_pgrsSetUploadSize(data, data->req.size); - next_state = SMB_UPLOAD; - } - else { - data->req.size = smb_swap64(smb_m->end_of_file); - if(data->req.size < 0) { - req->result = CURLE_WEIRD_SERVER_REPLY; - next_state = SMB_CLOSE; - } - else { - Curl_pgrsSetDownloadSize(data, data->req.size); - if(data->set.get_filetime) - get_posix_time(&data->info.filetime, smb_m->last_change_time); - next_state = SMB_DOWNLOAD; - } - } - break; - - case SMB_DOWNLOAD: - if(h->status || smbc->got < sizeof(struct smb_header) + 15) { - req->result = CURLE_RECV_ERROR; - next_state = SMB_CLOSE; - break; - } - len = Curl_read16_le((const unsigned char *)msg + - sizeof(struct smb_header) + 11); - off = Curl_read16_le((const unsigned char *)msg + - sizeof(struct smb_header) + 13); - if(len > 0) { - if(off + sizeof(unsigned int) + len > smbc->got) { - failf(data, "Invalid input packet"); - result = CURLE_RECV_ERROR; - } - else - result = Curl_client_write(data, CLIENTWRITE_BODY, - (char *)msg + off + sizeof(unsigned int), - len); - if(result) { - req->result = result; - next_state = SMB_CLOSE; - break; - } - } - data->req.offset += len; - next_state = (len < MAX_PAYLOAD_SIZE) ? SMB_CLOSE : SMB_DOWNLOAD; - break; - - case SMB_UPLOAD: - if(h->status || smbc->got < sizeof(struct smb_header) + 7) { - req->result = CURLE_UPLOAD_FAILED; - next_state = SMB_CLOSE; - break; - } - len = Curl_read16_le((const unsigned char *)msg + - sizeof(struct smb_header) + 5); - data->req.bytecount += len; - data->req.offset += len; - Curl_pgrs_upload_inc(data, len); - if(data->req.bytecount >= data->req.size) - next_state = SMB_CLOSE; - else - next_state = SMB_UPLOAD; - break; - - case SMB_CLOSE: - /* We do not care if the close failed, proceed to tree disconnect anyway */ - next_state = SMB_TREE_DISCONNECT; - break; - - case SMB_TREE_DISCONNECT: - next_state = SMB_DONE; - break; - - default: - smb_pop_message(smbc); - return CURLE_OK; /* ignore */ - } - - smb_pop_message(smbc); - - switch(next_state) { - case SMB_OPEN: - result = smb_send_open(data, smbc, req); - break; - - case SMB_DOWNLOAD: - result = smb_send_read(data, smbc, req); - break; - - case SMB_UPLOAD: - result = smb_send_write(data, smbc, req); - break; - - case SMB_CLOSE: - result = smb_send_close(data, smbc, req); - break; - - case SMB_TREE_DISCONNECT: - result = smb_send_tree_disconnect(data, smbc, req); - break; - - case SMB_DONE: - result = req->result; - *done = TRUE; - break; - - default: - break; - } - - if(result) { - CURL_TRC_M(data, "SMB: failed to send message"); - connclose(conn); - return result; - } - - request_state(data, next_state); - - return CURLE_OK; -} - -static CURLcode smb_pollset(struct Curl_easy *data, - struct easy_pollset *ps) -{ - return Curl_pollset_add_inout(data, ps, data->conn->sock[FIRSTSOCKET]); -} - -static CURLcode smb_do(struct Curl_easy *data, bool *done) -{ - struct connectdata *conn = data->conn; - struct smb_conn *smbc = Curl_conn_meta_get(conn, CURL_META_SMB_CONN); - - *done = FALSE; - if(!smbc) - return CURLE_FAILED_INIT; - if(smbc->share) - return CURLE_OK; - return CURLE_URL_MALFORMAT; -} - -/* - * SMB handler interface - */ -const struct Curl_protocol Curl_protocol_smb = { - smb_setup_connection, /* setup_connection */ - smb_do, /* do_it */ - ZERO_NULL, /* done */ - ZERO_NULL, /* do_more */ - smb_connect, /* connect_it */ - smb_connection_state, /* connecting */ - smb_request_state, /* doing */ - smb_pollset, /* proto_pollset */ - smb_pollset, /* doing_pollset */ - ZERO_NULL, /* domore_pollset */ - ZERO_NULL, /* perform_pollset */ - ZERO_NULL, /* disconnect */ - ZERO_NULL, /* write_resp */ - ZERO_NULL, /* write_resp_hd */ - ZERO_NULL, /* connection_is_dead */ - ZERO_NULL, /* attach connection */ - ZERO_NULL, /* follow */ -}; - -#endif /* CURL_ENABLE_SMB && USE_CURL_NTLM_CORE */ diff --git a/lib/smb.h b/lib/smb.h deleted file mode 100644 index 5a92ad13db3d..000000000000 --- a/lib/smb.h +++ /dev/null @@ -1,31 +0,0 @@ -#ifndef HEADER_CURL_SMB_H -#define HEADER_CURL_SMB_H -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) Bill Nagel , Exacq Technologies - * Copyright (C) Daniel Stenberg, , et al. - * - * This software is licensed as described in the file COPYING, which - * you should have received as part of this distribution. The terms - * are also available at https://curl.se/docs/copyright.html. - * - * You may opt to use, copy, modify, merge, publish, distribute and/or sell - * copies of the Software, and permit persons to whom the Software is - * furnished to do so, under the terms of the COPYING file. - * - * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY - * KIND, either express or implied. - * - * SPDX-License-Identifier: curl - * - ***************************************************************************/ -#if defined(CURL_ENABLE_SMB) && defined(USE_CURL_NTLM_CORE) -extern const struct Curl_protocol Curl_protocol_smb; -#endif - -#endif /* HEADER_CURL_SMB_H */ diff --git a/lib/urldata.h b/lib/urldata.h index 1633e337c4a3..f0c7af13020c 100644 --- a/lib/urldata.h +++ b/lib/urldata.h @@ -93,7 +93,6 @@ typedef CURLcode (Curl_recv)(struct Curl_easy *data, /* transfer */ #include "protocol.h" #include "ftp.h" #include "http.h" -#include "smb.h" #include "mqtt.h" #include "ftplistparser.h" #include "multihandle.h" diff --git a/lib/version.c b/lib/version.c index c4cdde6377bb..32d4498d0735 100644 --- a/lib/version.c +++ b/lib/version.c @@ -345,12 +345,6 @@ static const char * const supported_protocols[] = { "scp", "sftp", #endif -#if defined(CURL_ENABLE_SMB) && defined(USE_CURL_NTLM_CORE) - "smb", -# ifdef USE_SSL - "smbs", -# endif -#endif #ifndef CURL_DISABLE_SMTP "smtp", #endif diff --git a/projects/OS400/README.OS400 b/projects/OS400/README.OS400 index 52a64789e0c1..d1551dcfe72b 100644 --- a/projects/OS400/README.OS400 +++ b/projects/OS400/README.OS400 @@ -257,7 +257,6 @@ _ POP3 _ RTSP _ SCP if libssh2 is enabled _ SFTP if libssh2 is enabled -_ SMB _ SMTP _ TELNET _ TFTP diff --git a/scripts/cd2nroff b/scripts/cd2nroff index 02f97bc41efc..2dbc1a316e2a 100755 --- a/scripts/cd2nroff +++ b/scripts/cd2nroff @@ -175,8 +175,6 @@ my %knownprotos = ( 'RTSP' => 1, 'SCP' => 1, 'SFTP' => 1, - 'SMB' => 1, - 'SMBS' => 1, 'SMTP' => 1, 'SMTPS' => 1, 'TELNET' => 1, diff --git a/tests/Makefile.am b/tests/Makefile.am index 8273f83bb9c7..ba293f2b8543 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -66,14 +66,12 @@ EXTRA_DIST = \ nghttpx.conf \ pathhelp.pm \ processhelp.pm \ - requirements.txt \ rtspserver.pl \ runner.pm \ runtests.pl \ secureserver.pl \ serverhelp.pm \ servers.pm \ - smbserver.py \ sshhelp.pm \ sshserver.pl \ testcurl.pl \ diff --git a/tests/data/Makefile.am b/tests/data/Makefile.am index 0c99ae63b570..8577e0226a22 100644 --- a/tests/data/Makefile.am +++ b/tests/data/Makefile.am @@ -25,9 +25,6 @@ all: install: test: -# The helper script ./scripts/testnum provides a set of available test -# numbers. - # this list is in numerical order TESTCASES = \ test1 \ @@ -1472,7 +1469,6 @@ TESTCASES = \ test1448 \ test1449 \ test1450 \ - test1451 \ test1452 \ test1453 \ test1454 \ @@ -1740,7 +1736,6 @@ TESTCASES = \ test1847 \ test1848 \ test1849 \ - test1850 \ test1851 \ test1862 \ test1900 \ diff --git a/tests/data/test1451 b/tests/data/test1451 deleted file mode 100644 index d0fdf56f7243..000000000000 --- a/tests/data/test1451 +++ /dev/null @@ -1,38 +0,0 @@ - - - - -SMB - - - -# Server-side - - -Basic SMB test complete - - - -# Client-side - - -smb - - -smb - - -Basic SMB request - - --u 'curltest:curltest' smb://%HOSTIP:%SMBPORT/TESTS/%TESTNUMBER - - - -# Verify data after the test has been "shot" - - -Basic SMB test complete - - - diff --git a/tests/data/test1627 b/tests/data/test1627 index 95fd47d83fb2..94079f9c5b49 100644 --- a/tests/data/test1627 +++ b/tests/data/test1627 @@ -18,7 +18,7 @@ Curl_get_scheme unit test -547 invokes +541 invokes diff --git a/tests/data/test1850 b/tests/data/test1850 deleted file mode 100644 index 6a3e4d409b0a..000000000000 --- a/tests/data/test1850 +++ /dev/null @@ -1,39 +0,0 @@ - - - - -SMB - - - -# Server-side - - -Basic SMB test complete - - - -# Client-side - - -smb - - -smb - - -Two SMB requests in the same command line - - --u 'curltest:curltest' smb://%HOSTIP:%SMBPORT/TESTS/%TESTNUMBER smb://%HOSTIP:%SMBPORT/TESTS/%TESTNUMBER - - - -# Verify data after the test has been "shot" - - -Basic SMB test complete -Basic SMB test complete - - - diff --git a/tests/requirements.txt b/tests/requirements.txt deleted file mode 100644 index abf6918bb334..000000000000 --- a/tests/requirements.txt +++ /dev/null @@ -1,5 +0,0 @@ -# Copyright (C) Daniel Stenberg, , et al. -# -# SPDX-License-Identifier: curl - -impacket==0.13.1 diff --git a/tests/serverhelp.pm b/tests/serverhelp.pm index ac451cb04b7f..ccb9476ac630 100644 --- a/tests/serverhelp.pm +++ b/tests/serverhelp.pm @@ -123,7 +123,7 @@ sub servername_str { $proto = uc($proto) if($proto); die "unsupported protocol: '$proto'" unless($proto && - ($proto =~ /^(((DNS|FTP|HTTP|HTTP\/2|HTTP\/3|IMAP|POP3|GOPHER|SMTP|HTTPS-MTLS)S?)|(TFTP|SFTP|SOCKS|SSH|RTSP|DICT|SMB|SMBS|TELNET|MQTT|MQTTS))$/)); + ($proto =~ /^(((DNS|FTP|HTTP|HTTP\/2|HTTP\/3|IMAP|POP3|GOPHER|SMTP|HTTPS-MTLS)S?)|(TFTP|SFTP|SOCKS|SSH|RTSP|DICT|TELNET|MQTT|MQTTS))$/)); $ipver = (not $ipver) ? 'ipv4' : lc($ipver); die "unsupported IP version: '$ipver'" unless($ipver && diff --git a/tests/servers.pm b/tests/servers.pm index 99db988124e1..479824031977 100644 --- a/tests/servers.pm +++ b/tests/servers.pm @@ -235,7 +235,7 @@ sub init_serverpidfile_hash { } } for my $proto (('tftp', 'sftp', 'socks', 'ssh', 'rtsp', - 'dict', 'smb', 'smbs', 'telnet', 'mqtt', 'mqtts', + 'dict', 'telnet', 'mqtt', 'mqtts', 'https-mtls', 'dns')) { for my $ipvnum ((4, 6)) { for my $idnum ((1, 2)) { @@ -797,66 +797,6 @@ sub verifypid { return $pid; } -####################################################################### -# Verify that the server that runs on $ip, $port is our server. This also -# implies that we can speak with it, as there might be occasions when the -# server runs fine but we cannot talk to it ("Failed to connect to ::1: Cannot -# assign requested address") -# -sub verifysmb { - my ($proto, $ipvnum, $idnum, $ip, $port) = @_; - my $server = servername_id($proto, $ipvnum, $idnum); - my $time = time(); - my $extra = ""; - - my $verifylog = "$LOGDIR/". - servername_canon($proto, $ipvnum, $idnum) .'_verify.log'; - unlink($verifylog) if(-f $verifylog); - - my $flags = "--max-time $server_response_maxtime "; - $flags .= "--silent "; - $flags .= "--verbose "; - $flags .= "--globoff "; - $flags .= "-u 'curltest:curltest' "; - $flags .= $extra; - $flags .= "\"$proto://$ip:$port/SERVER/verifiedserver\""; - - my $cmd = exerunner() . "$VCURL $flags 2>$verifylog"; - - # check if this is our server running on this port: - logmsg "RUN: $cmd\n" if($verbose); - my @data = runclientoutput($cmd); - - my $res = $? >> 8; # rotate the result - if($res & 128) { - logmsg "RUN: curl command died with a coredump\n"; - return -1; - } - - my $pid = 0; - foreach my $line (@data) { - if($line =~ /WE ROOLZ: (\d+)/) { - # this is our test server with a known pid! - $pid = 0+$1; - last; - } - } - if($pid <= 0 && @data && $data[0]) { - # this is not a known server - logmsg "RUN: Unknown server on our $server port: $port\n"; - return 0; - } - # we can/should use the time it took to verify the server as a measure - # on how fast/slow this host is. - my $took = int(0.5+time()-$time); - - if($verbose) { - logmsg "RUN: Verifying our test $server server took $took seconds\n"; - } - - return $pid; -} - ####################################################################### # Verify that the server that runs on $ip, $port is our server. This also # implies that we can speak with it, as there might be occasions when the @@ -950,7 +890,6 @@ my %protofunc = ('http' => \&verifyhttp, 'socks5unix' => \&verifypid, 'gopher' => \&verifyhttp, 'dict' => \&verifyftp, - 'smb' => \&verifysmb, 'telnet' => \&verifytelnet); ####################################################################### @@ -1973,67 +1912,6 @@ sub rundictserver { return (0+!$dictpid, $dictpid, $pid2, $port); } -####################################################################### -# start the SMB server -# -sub runsmbserver { - my ($verb, $alt) = @_; - my $proto = "smb"; - my $ip = $HOSTIP; - my $ipvnum = 4; - my $idnum = 1; - - if($alt eq "ipv6") { - # No IPv6 - } - - my $server = servername_id($proto, $ipvnum, $idnum); - - my $pidfile = $serverpidfile{$server}; - - # do not retry if the server does not work - if($doesntrun{$pidfile}) { - return (2, 0, 0, 0); - } - - my $pid = processexists($pidfile); - if($pid > 0) { - stopserver($server, $pid); - } - unlink($pidfile) if(-f $pidfile); - - my $srvrname = servername_str($proto, $ipvnum, $idnum); - my $logfile = server_logfilename($LOGDIR, $proto, $ipvnum, $idnum); - - my $flags = ""; - $flags .= "--verbose 1 " if($debugprotocol); - $flags .= "--pidfile \"$pidfile\" --logfile \"$logfile\" "; - $flags .= "--id $idnum " if($idnum > 1); - $flags .= "--srcdir \"$srcdir\" "; - $flags .= "--host $HOSTIP"; - - my $port = getfreeport($ipvnum); - my $aflags = "--port $port $flags"; - my $cmd = "$srcdir/smbserver.py $aflags"; - my ($smbpid, $pid2) = startnew($cmd, $pidfile, 15, 0); - - if($smbpid <= 0 || !pidexists($smbpid)) { - # it is NOT alive - stopserver($server, $pid2); - $doesntrun{$pidfile} = 1; - $smbpid = $pid2 = 0; - logmsg "RUN: failed to start the $srvrname server\n"; - return (3, 0, 0, 0); - } - $doesntrun{$pidfile} = 0; - - if($verb) { - logmsg "RUN: $srvrname server PID $smbpid port $port\n"; - } - - return (0+!$smbpid, $smbpid, $pid2, $port); -} - ####################################################################### # start the telnet server # @@ -2817,17 +2695,6 @@ sub startservers { $run{'dict'} = "$pid $pid2"; } } - elsif($what eq "smb") { - if(!$run{'smb'}) { - ($serr, $pid, $pid2, $PORT{"smb"}) = runsmbserver($verbose, ""); - if($pid <= 0) { - return ("failed starting SMB server", $serr); - } - logmsg sprintf ("* pid SMB => %d %d\n", $pid, $pid2) - if($verbose); - $run{'smb'} = "$pid $pid2"; - } - } elsif($what eq "telnet") { if(!$run{'telnet'}) { ($serr, $pid, $pid2, $PORT{"telnet"}) = @@ -2930,7 +2797,6 @@ sub subvariables { 'NOLISTEN', 'POP3', 'POP36', 'POP3S', 'RTSP', 'RTSP6', - 'SMB', 'SMBS', 'SMTP', 'SMTP6', 'SMTPS', 'SOCKS', 'SSH', diff --git a/tests/smbserver.py b/tests/smbserver.py deleted file mode 100755 index d14e7ff1d3cd..000000000000 --- a/tests/smbserver.py +++ /dev/null @@ -1,440 +0,0 @@ -#!/usr/bin/env python3 -# -# Project ___| | | | _ \| | -# / __| | | | |_) | | -# | (__| |_| | _ <| |___ -# \___|\___/|_| \_\_____| -# -# Copyright (C) Daniel Stenberg, , et al. -# -# This software is licensed as described in the file COPYING, which -# you should have received as part of this distribution. The terms -# are also available at https://curl.se/docs/copyright.html. -# -# You may opt to use, copy, modify, merge, publish, distribute and/or sell -# copies of the Software, and permit persons to whom the Software is -# furnished to do so, under the terms of the COPYING file. -# -# This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY -# KIND, either express or implied. -# -# SPDX-License-Identifier: curl -# -"""Server for testing SMB.""" - -import argparse -import configparser -import importlib.util -import logging -import os -import signal -import sys -import tempfile -import threading - -# Import our curl test data helper -from util import ClosingFileHandler, TestData - -# impacket needs to be installed in the Python environment -if importlib.util.find_spec('impacket') is None: - sys.stderr.write( - 'Warning: Python package impacket is required for smb testing; ' - 'use pip or your package manager to install it\n') - sys.exit(1) - -from impacket import smb as imp_smb -from impacket import smbserver as imp_smbserver -from impacket.nt_errors import STATUS_ACCESS_DENIED, STATUS_NO_SUCH_FILE, STATUS_SUCCESS - -log = logging.getLogger(__name__) -SERVER_MAGIC = "SERVER_MAGIC" -TESTS_MAGIC = "TESTS_MAGIC" -VERIFIED_REQ = "verifiedserver" -VERIFIED_RSP = "WE ROOLZ: {pid}\n" - - -class ShutdownHandler(threading.Thread): - """ - Cleanly shut down the SMB server. - - This can only be done from another thread while the server is in - serve_forever(), so a thread is spawned here that waits for a shutdown - signal before doing its thing. Use in a with statement around the - serve_forever() call. - """ - - def __init__(self, server): - super().__init__() - self.server = server - self.shutdown_event = threading.Event() - - def __enter__(self): - self.start() - signal.signal(signal.SIGINT, self._sighandler) - signal.signal(signal.SIGTERM, self._sighandler) - - def __exit__(self, *_): - # Call for shutdown in case it was not done already - self.shutdown_event.set() - # Wait for thread, and therefore also the server, to finish - self.join() - # Uninstall our signal handlers - signal.signal(signal.SIGINT, signal.SIG_DFL) - signal.signal(signal.SIGTERM, signal.SIG_DFL) - # Delete any temporary files created by the server during its run - log.info("Deleting %d temporary file(s)", len(self.server.tmpfiles)) - for f in self.server.tmpfiles: - os.unlink(f) - - def _sighandler(self, _signum, _frame): - # Wake up the cleanup task - self.shutdown_event.set() - - def run(self): - # Wait for shutdown signal - self.shutdown_event.wait() - # Notify the server to shut down - self.server.shutdown() - - -def smbserver(options): - """Start up a TCP SMB server that serves forever.""" - if options.pidfile: - pid = os.getpid() - # see tests/server/util.c function write_pidfile - if os.name == "nt": - pid += 4194304 - with open(options.pidfile, "w") as f: - f.write(str(pid)) - - # Here we write a mini config for the server - smb_config = configparser.ConfigParser() - smb_config.add_section("global") - smb_config.set("global", "server_name", "SERVICE") - smb_config.set("global", "server_os", "UNIX") - smb_config.set("global", "server_domain", "WORKGROUP") - smb_config.set("global", "log_file", "None") - smb_config.set("global", "credentials_file", "") - - # We need a share which allows us to test that the server is running - smb_config.add_section("SERVER") - smb_config.set("SERVER", "comment", "server function") - smb_config.set("SERVER", "read only", "yes") - smb_config.set("SERVER", "share type", "0") - smb_config.set("SERVER", "path", SERVER_MAGIC) - - # Have a share for tests. These files are auto-generated from the - # test input. - smb_config.add_section("TESTS") - smb_config.set("TESTS", "comment", "tests") - smb_config.set("TESTS", "read only", "yes") - smb_config.set("TESTS", "share type", "0") - smb_config.set("TESTS", "path", TESTS_MAGIC) - - if not options.srcdir or not os.path.isdir(options.srcdir): - raise ScriptError("--srcdir is mandatory") - - test_data_dir = os.path.join(options.srcdir, "data") - - smb_server = TestSmbServer((options.host, options.port), - config_parser=smb_config, - test_data_directory=test_data_dir) - log.info("[SMB] setting up SMB server on port %s", options.port) - smb_server.processConfigFile() - - # Start a thread that cleanly shuts down the server on a signal - with ShutdownHandler(smb_server): - # This blocks until smb_server.shutdown() is called - smb_server.serve_forever() - - return 0 - - -class TestSmbServer(imp_smbserver.SMBSERVER): - """ - Test server for SMB. - - It subclasses the impacket SMBSERVER and provides test functionality. - """ - - def __init__(self, - address, - config_parser=None, - test_data_directory=None): - imp_smbserver.SMBSERVER.__init__(self, - address, - config_parser=config_parser) - self.tmpfiles = [] - - # Set up a test data object so we can get test data later. - self.ctd = TestData(test_data_directory) - - # Override smbComNtCreateAndX so we can pretend to have files which - # do not exist. - self.hookSmbCommand(imp_smb.SMB.SMB_COM_NT_CREATE_ANDX, - self.create_and_x) - - def create_and_x(self, conn_id, smb_server, smb_command, recv_packet): - """ - Our version of smbComNtCreateAndX. - - It looks for special test files and fools the rest of the framework - into opening them as if they were normal files. - """ - conn_data = smb_server.getConnectionData(conn_id) - - # Wrap processing in a try block which allows us to throw SmbError - # to control the flow. - try: - ncax_params = imp_smb.SMBNtCreateAndX_Parameters( - smb_command["Parameters"]) - - path = self.get_share_path(conn_data, - ncax_params["RootFid"], - recv_packet["Tid"]) - log.info("[SMB] Requested share path: %s", path) - - disposition = ncax_params["Disposition"] - log.debug("[SMB] Requested disposition: %s", disposition) - - # Currently we only support reading files. - if disposition != imp_smb.FILE_OPEN: - raise SmbError(STATUS_ACCESS_DENIED, "Only support reading files") - - # Check to see if the path we were given is actually a - # magic path which needs generating on the fly. - if path not in [SERVER_MAGIC, TESTS_MAGIC]: - # Pass the command onto the original handler. - return imp_smbserver.SMBCommands.smbComNtCreateAndX(conn_id, - smb_server, - smb_command, - recv_packet) - - flags2 = recv_packet["Flags2"] - ncax_data = imp_smb.SMBNtCreateAndX_Data(flags=flags2, - data=smb_command["Data"]) - requested_file = imp_smbserver.decodeSMBString( - flags2, - ncax_data["FileName"]) - log.debug("[SMB] User requested file '%s'", requested_file) - - if path == SERVER_MAGIC: - fid, full_path = self.get_server_path(requested_file) - else: - assert path == TESTS_MAGIC - fid, full_path = self.get_test_path(requested_file) - - self.tmpfiles.append(full_path) - - resp_params = imp_smb.SMBNtCreateAndXResponse_Parameters() - resp_data = "" - - # Simple way to generate a fid - if len(conn_data["OpenedFiles"]) == 0: - fakefid = 1 - else: - fakefid = max(conn_data["OpenedFiles"].keys()) + 1 - resp_params["Fid"] = fakefid - resp_params["CreateAction"] = disposition - - if os.path.isdir(path): - resp_params[ - "FileAttributes"] = imp_smb.SMB_FILE_ATTRIBUTE_DIRECTORY - resp_params["IsDirectory"] = 1 - else: - resp_params["IsDirectory"] = 0 - resp_params["FileAttributes"] = ncax_params["FileAttributes"] - - # Get this file's information - resp_info, error_code = imp_smbserver.queryPathInformation( - os.path.dirname(full_path), os.path.basename(full_path), - level=imp_smb.SMB_QUERY_FILE_ALL_INFO) - - if error_code != STATUS_SUCCESS: - raise SmbError(error_code, "Failed to query path info") - - resp_params["CreateTime"] = resp_info["CreationTime"] - resp_params["LastAccessTime"] = resp_info[ - "LastAccessTime"] - resp_params["LastWriteTime"] = resp_info["LastWriteTime"] - resp_params["LastChangeTime"] = resp_info[ - "LastChangeTime"] - resp_params["FileAttributes"] = resp_info[ - "ExtFileAttributes"] - resp_params["AllocationSize"] = resp_info[ - "AllocationSize"] - resp_params["EndOfFile"] = resp_info["EndOfFile"] - - # Let's store the fid for the connection - # smbServer.log("Create file %s, mode:0x%x" % (pathName, mode)) - conn_data["OpenedFiles"][fakefid] = {} - conn_data["OpenedFiles"][fakefid]["FileHandle"] = fid - conn_data["OpenedFiles"][fakefid]["FileName"] = path - conn_data["OpenedFiles"][fakefid]["DeleteOnClose"] = False - - except SmbError as s: - log.debug("[SMB] SmbError hit: %s", s) - error_code = s.error_code - resp_params = "" - resp_data = "" - - resp_cmd = imp_smb.SMBCommand(imp_smb.SMB.SMB_COM_NT_CREATE_ANDX) - resp_cmd["Parameters"] = resp_params - resp_cmd["Data"] = resp_data - smb_server.setConnectionData(conn_id, conn_data) - - return [resp_cmd], None, error_code - - def get_share_path(self, conn_data, root_fid, tid): - conn_shares = conn_data["ConnectedShares"] - - if tid in conn_shares: - if root_fid > 0: - # If we have a rootFid, the path is relative to that fid - path = conn_data["OpenedFiles"][root_fid]["FileName"] - log.debug(f'RootFid present {path}!') - return path - - if "path" in conn_shares[tid]: - return conn_shares[tid]["path"] - - raise SmbError(STATUS_ACCESS_DENIED, "Connection share had no path") - raise SmbError(imp_smbserver.STATUS_SMB_BAD_TID, "TID was invalid") - - def get_server_path(self, requested_filename): - log.debug("[SMB] Get server path '%s'", requested_filename) - - if requested_filename != VERIFIED_REQ: - raise SmbError(STATUS_NO_SUCH_FILE, "Could not find the file") - - fid, filename = tempfile.mkstemp() - log.debug("[SMB] Created %s (%d) for storing '%s'", - filename, fid, requested_filename) - - contents = b'' - - if requested_filename == VERIFIED_REQ: - log.debug("[SMB] Verifying server is alive") - pid = os.getpid() - # see tests/server/util.c function write_pidfile - if os.name == "nt": - pid += 4194304 - contents = VERIFIED_RSP.format(pid=pid).encode('utf-8') - - self.write_to_fid(fid, contents) - return fid, filename - - def write_to_fid(self, fid, contents): - # Write the contents to file descriptor - os.write(fid, contents) - os.fsync(fid) - - # Rewind the file to the beginning so a read gets us the contents - os.lseek(fid, 0, os.SEEK_SET) - - def get_test_path(self, requested_filename): - log.info("[SMB] Get reply data from 'test%s'", requested_filename) - - fid, filename = tempfile.mkstemp() - log.debug("[SMB] Created %s (%d) for storing test '%s'", - filename, fid, requested_filename) - - try: - contents = self.ctd.get_test_data(requested_filename).encode('utf-8') - self.write_to_fid(fid, contents) - return fid, filename - - except Exception as e: - log.exception("Failed to make test file") - raise SmbError(STATUS_NO_SUCH_FILE, "Failed to make test file") from e - - -class SmbError(Exception): - def __init__(self, error_code, error_message): - super().__init__(error_message) - self.error_code = error_code - - -class ScriptRC: - """Enum for script return codes.""" - - SUCCESS = 0 - FAILURE = 1 - EXCEPTION = 2 - - -class ScriptError(Exception): - pass - - -def get_options(): - parser = argparse.ArgumentParser() - - parser.add_argument("--port", action="store", default=9017, - type=int, help="port to listen on") - parser.add_argument("--host", action="store", default="127.0.0.1", - help="host to listen on") - parser.add_argument("--verbose", action="store", type=int, default=0, - help="verbose output") - parser.add_argument("--pidfile", action="store", - help="filename for the PID") - parser.add_argument("--logfile", action="store", - help="filename for the log") - parser.add_argument("--srcdir", action="store", help="test directory") - parser.add_argument("--id", action="store", help="server ID") - parser.add_argument("--ipv4", action="store_true", default=0, - help="IPv4 flag") - - return parser.parse_args() - - -def setup_logging(options): - """Set up logging from the command line options.""" - root_logger = logging.getLogger() - add_stdout = False - - formatter = logging.Formatter("%(asctime)s %(levelname)-5.5s %(message)s") - - # Write out to a logfile - if options.logfile: - handler = ClosingFileHandler(options.logfile) - handler.setFormatter(formatter) - handler.setLevel(logging.DEBUG) - root_logger.addHandler(handler) - else: - # The logfile was not specified. Add a stdout logger. - add_stdout = True - - if options.verbose: - # Add a stdout logger as well in verbose mode - root_logger.setLevel(logging.DEBUG) - add_stdout = True - else: - root_logger.setLevel(logging.WARNING) - - if add_stdout: - stdout_handler = logging.StreamHandler(sys.stdout) - stdout_handler.setFormatter(formatter) - stdout_handler.setLevel(logging.DEBUG) - root_logger.addHandler(stdout_handler) - - -if __name__ == '__main__': - # Get the options from the user. - options = get_options() - - # Setup logging using the user options - setup_logging(options) - - # Run main script. - try: - rc = smbserver(options) - except Exception: - log.exception('Error in SMB server') - rc = ScriptRC.EXCEPTION - - if options.pidfile and os.path.isfile(options.pidfile): - os.unlink(options.pidfile) - - log.info("[SMB] Returning %d", rc) - sys.exit(rc) diff --git a/tests/unit/unit1627.c b/tests/unit/unit1627.c index 5150688df817..6f96c0a9003e 100644 --- a/tests/unit/unit1627.c +++ b/tests/unit/unit1627.c @@ -38,18 +38,15 @@ static CURLcode test_unit1627(const char *arg) /* all upper */ "DICT", "FILE", "FTP", "FTPS", "GOPHER", "GOPHERS", "HTTP", "HTTPS", "IMAP", "IMAPS", "LDAP", "LDAPS", "MQTT", "MQTTS", "POP3", "POP3S", - "RTSP", "SCP", "SFTP", "SMB", "SMBS", "SMTP", "SMTPS", - "TELNET", "TFTP", "WS", "WSS", + "RTSP", "SCP", "SFTP", "SMTP", "SMTPS", "TELNET", "TFTP", "WS", "WSS", /* all lower */ "dict", "file", "ftp", "ftps", "gopher", "gophers", "http", "https", "imap", "imaps", "ldap", "ldaps", "mqtt", "mqtts", "pop3", "pop3s", - "rtsp", "scp", "sftp", "smb", "smbs", "smtp", "smtps", - "telnet", "tftp", "ws", "wss", + "rtsp", "scp", "sftp", "smtp", "smtps", "telnet", "tftp", "ws", "wss", /* mixed */ "diCt", "fIle", "Ftp", "ftpS", "Gopher", "gOphers", "htTp", "httPs", "imAP", "imaPS", "LDap", "LDAps", "mQTT", "mqtTS", "pOP3", "pOP3s", - "RtsP", "ScP", "SFtP", "Smb", "smBS", "sMTP", "SMTPs", - "TELNEt", "tFTP", "Ws", "wSS", + "RtsP", "ScP", "SFtP", "sMTP", "SMTPs", "TELNEt", "tFTP", "Ws", "wSS", "SOCKS", "SOCKS4", "SOCKS5", "SOCKS4A", "SOCKS5H", "socks", "socks4", "socks5", "socks4a", "socks5h", From 0e27276b06d60bf0b01197ef52b0f40a22f72098 Mon Sep 17 00:00:00 2001 From: Max Dymond Date: Sat, 12 Sep 2026 08:16:48 +0100 Subject: [PATCH 116/481] netrc: empty quoted values now produce a valid empty string Detected by: OSS-Fuzz Bug: https://issues.oss-fuzz.com/issues/560352522 Closes #22921 --- lib/netrc.c | 4 ++- tests/unit/unit1304.c | 57 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+), 1 deletion(-) diff --git a/lib/netrc.c b/lib/netrc.c index 7991c935487a..b416f376209e 100644 --- a/lib/netrc.c +++ b/lib/netrc.c @@ -223,7 +223,9 @@ static NETRCcode netrc_lexer_quoted(struct netrc_lexer *lexer) } else if(c == '\"') { ++s; /* pass the ending quote */ - rc = NETRC_OK; + /* Ensure even an empty literal has a terminating NUL. */ + result = curlx_dyn_addn(&lexer->literal, "", 0); + rc = curl2netrc(result); goto out; } result = curlx_dyn_addn(&lexer->literal, &c, 1); diff --git a/tests/unit/unit1304.c b/tests/unit/unit1304.c index 0c788818a5cf..6f9526b491b7 100644 --- a/tests/unit/unit1304.c +++ b/tests/unit/unit1304.c @@ -56,6 +56,61 @@ static bool t1304_no_passwd(struct Curl_creds *creds) return !creds || !creds->passwd[0]; } +static void t1304_empty_literals(struct Curl_easy *data, const char *filename) +{ + static const struct { + const char *content; + NETRCcode result; + const char *login; + const char *passwd; + } tests[] = { + { "default login \"\"", NETRC_OK, "", "" }, + { "default password \"\"", NETRC_OK, "", "" }, + { "default login \"\" password secret", NETRC_OK, "", "secret" }, + { "default password \"\" login admin", NETRC_OK, "admin", "" }, + { "default login \"\" password \"\"", NETRC_OK, "", "" }, + { "machine \"\" login ignored password ignored", + NETRC_NO_MATCH, NULL, NULL }, + { "machine \"\" login ignored password ignored\n" + "machine example.com login admin password passwd", + NETRC_OK, "admin", "passwd" }, + { "machine example.com login \"\" password \"\"", NETRC_OK, "", "" } + }; + size_t i; + + for(i = 0; i < CURL_ARRAYSIZE(tests); ++i) { + struct store_netrc store; + struct Curl_creds *creds = NULL; + CURLcode result; + NETRCcode res; + + /* Each cached file starts a fresh lexer, so an empty first literal + * cannot rely on an allocation from a previous nonempty literal. */ + Curl_netrc_init(&store); + result = curlx_dyn_add(&store.filebuf, tests[i].content); + fail_unless(result == CURLE_OK, "could not store netrc content"); + store.filename = curlx_strdup(filename); + fail_unless(store.filename, "could not store netrc filename"); + if(!result && store.filename) { + store.loaded = TRUE; + res = Curl_netrc_scan(data, &store, "example.com", NULL, + filename, &creds); + fail_unless(res == tests[i].result, "unexpected netrc result"); + if(tests[i].result == NETRC_OK) { + fail_unless(creds, "credentials should have been found"); + fail_unless(!strcmp(Curl_creds_user(creds), tests[i].login), + "unexpected login"); + fail_unless(!strcmp(Curl_creds_passwd(creds), tests[i].passwd), + "unexpected password"); + } + else + fail_unless(!creds, "credentials should not have been found"); + } + Curl_creds_unlink(&creds); + Curl_netrc_cleanup(&store); + } +} + static CURLcode test_unit1304(const char *arg) { struct Curl_creds *cr_out = NULL; @@ -152,6 +207,8 @@ static CURLcode test_unit1304(const char *arg) Curl_creds_unlink(&cr_out); + t1304_empty_literals(data, arg); + UNITTEST_END(t1304_stop(data)) } From efd42dfbbf656870f8885815d07b0d16a5771f69 Mon Sep 17 00:00:00 2001 From: Viktor Szakats Date: Sat, 12 Sep 2026 16:43:46 +0200 Subject: [PATCH 117/481] tidy-up: drop mentions of `impacket` Follow-up to 7bbaf2fffd947a1b7d1b96993caf00b9452200bd #22652 Closes #22925 --- .github/scripts/pyspelling.words | 1 - docs/tests/TEST-SUITE.md | 16 ---------------- 2 files changed, 17 deletions(-) diff --git a/.github/scripts/pyspelling.words b/.github/scripts/pyspelling.words index e42c621acb58..41c48d85ced1 100644 --- a/.github/scripts/pyspelling.words +++ b/.github/scripts/pyspelling.words @@ -393,7 +393,6 @@ IMAP imap IMAPS imaps -impacket implementers INI init diff --git a/docs/tests/TEST-SUITE.md b/docs/tests/TEST-SUITE.md index 465738952973..313effb43bf1 100644 --- a/docs/tests/TEST-SUITE.md +++ b/docs/tests/TEST-SUITE.md @@ -53,22 +53,6 @@ output from the servers used in the test. - `openssh` or `SunSSH` (for SCP and SFTP tests) - `nghttpx` (for HTTP/2 and HTTP/3 tests) -### Installation of impacket - -The Python-based test servers support Python 3. - -Please install python-impacket in the correct Python environment. You can -use pip or your OS' package manager to install 'impacket'. - -On Debian/Ubuntu the package name is 'python3-impacket' - -On FreeBSD the package name is 'py311-impacket' - -On any system where pip is available: 'python3 -m pip install impacket' - -You may also need to manually install the Python package 'six' as that may -be a missing requirement for impacket. - ## Event-based If curl is built with `Debug` enabled (see below), then the `runtests.pl` From a724199d27dbe8bf2fc3aed7776e30ea0932bc25 Mon Sep 17 00:00:00 2001 From: Dan Fandrich Date: Sat, 12 Sep 2026 21:17:16 -0700 Subject: [PATCH 118/481] docs: mention scripts/testnum in the test suite docs --- docs/tests/TEST-SUITE.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/tests/TEST-SUITE.md b/docs/tests/TEST-SUITE.md index 313effb43bf1..c648614b0efe 100644 --- a/docs/tests/TEST-SUITE.md +++ b/docs/tests/TEST-SUITE.md @@ -256,6 +256,7 @@ up. Subsets of tests can now be selected by passing keywords to the runtests.pl script via the make `TFLAGS` variable. New tests are added by finding a free number in `tests/data/Makefile.am`. +The script `scripts/testnum` automates this search for you. ## Write tests From 3654c8dc85b998a645d41e50108e29d10439e484 Mon Sep 17 00:00:00 2001 From: Viktor Szakats Date: Sun, 13 Sep 2026 11:33:42 +0200 Subject: [PATCH 119/481] GHA/non-native: bump cross-platform-actions, FreeBSD 14.5, enable DragonFly BSD jobs Try DragonFly BSD again, after upstream fix. Ref: https://github.com/cross-platform-actions/action/releases/tag/v1.6.0 Follow-up to bf29c3e17544ff13c67e94157e6440662317355b #22145 Closes #22931 --- .github/workflows/non-native.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/non-native.yml b/.github/workflows/non-native.yml index 7cd795ec1642..2b5f0b0b3cdc 100644 --- a/.github/workflows/non-native.yml +++ b/.github/workflows/non-native.yml @@ -60,12 +60,12 @@ jobs: matrix: include: # https://github.com/DragonFlyBSD/DPorts - # { os: 'dragonflybsd', version: '6.4.2', build: 'autotools', arch: 'x86_64' , cc: 'gcc' , desc: 'openssl', tflags: 'skiprun', - # install: 'autoconf automake libtool openldap26-client libidn2', - # options: '--with-openssl --enable-ldap --enable-ldaps --with-libidn2' } + - { os: 'dragonflybsd', version: '6.4.2', build: 'autotools', arch: 'x86_64' , cc: 'gcc' , desc: 'openssl', tflags: 'skiprun', + install: 'autoconf automake libtool openldap26-client libidn2', + options: '--with-openssl --enable-ldap --enable-ldaps --with-libidn2' } - # { os: 'dragonflybsd', version: '6.4.2', build: 'cmake' , arch: 'x86_64' , cc: 'gcc' , desc: 'openssl', tflags: 'skipall', - # install: 'cmake ninja' } + - { os: 'dragonflybsd', version: '6.4.2', build: 'cmake' , arch: 'x86_64' , cc: 'gcc' , desc: 'openssl', tflags: 'skipall', + install: 'cmake ninja' } # https://ports.freebsd.org/ - { os: 'freebsd' , version: '15.1', build: 'autotools', arch: 'x86_64' , cc: 'clang', desc: 'openssl', @@ -84,7 +84,7 @@ jobs: install: 'autoconf automake libtool krb5-devel openldap26-client libidn2 stunnel', options: '--with-openssl --with-gssapi --enable-ldap --enable-ldaps --with-libidn2 --disable-typecheck' } - - { os: 'freebsd' , version: '14.3', build: 'cmake' , arch: 'arm64' , cc: 'clang', desc: 'openssl', tflags: 'skiprun', + - { os: 'freebsd' , version: '14.5', build: 'cmake' , arch: 'arm64' , cc: 'clang', desc: 'openssl', tflags: 'skiprun', install: 'cmake-core ninja perl5 krb5-devel openldap26-client libidn2 stunnel', options: '-DCURL_USE_GSSAPI=ON -DCURL_DISABLE_TYPECHECK=ON' } @@ -125,7 +125,7 @@ jobs: persist-credentials: false - name: 'setup VM' - uses: cross-platform-actions/action@faa0c6197e94aacf1c5956460152c8380d3560a5 # v1.5.0 + uses: cross-platform-actions/action@e0b9770014ba65d5e0815f15b74031c3a635f641 # v1.6.0 with: environment_variables: 'CC CURL_CI CURL_TEST_MIN DO_NOT_TRACK MAKEFLAGS MATRIX_ARCH MATRIX_BUILD MATRIX_INSTALL MATRIX_OPTIONS MATRIX_OS MATRIX_VERSION TFLAGS' operating_system: '${{ matrix.os }}' From 0b04700029149d740f50b7925dd162aa270e3908 Mon Sep 17 00:00:00 2001 From: Daniel Stenberg Date: Sat, 12 Sep 2026 14:28:10 +0200 Subject: [PATCH 120/481] urlapi: run the urlparser perf test faster - Batch contiguous unencoded characters into single curlx_dyn_addn() calls instead of invoking dynbuf append byte-by-byte - Inline lowercasing in absolute url parsing - Make junkscan pass over data only once instead of twice - Use lookup-table to validate hostname in hostname_check() - Improve needs_dedotdot with leading memchr calls for dots and percent - Replace a strcspn() call with a while loop - Replace several strchr with memchr - Detect IPv6 earlier and skip some logic - Skip the full URL junkscan: - allows for better return codes for where the problem lies - allows for proper support for the URLENCODE flag - runs a little faster This completes the perf test roughly 17% faster on my machine in debug mode. Closes #22924 --- lib/urlapi.c | 222 +++++++++++++++++++++++++--------------- lib/vdns/doh.c | 17 ++- tests/libtest/lib1560.c | 54 ++++++---- 3 files changed, 185 insertions(+), 108 deletions(-) diff --git a/lib/urlapi.c b/lib/urlapi.c index c7bb3539de24..1b1e6fcafbac 100644 --- a/lib/urlapi.c +++ b/lib/urlapi.c @@ -55,9 +55,6 @@ #define MAX_SCHEME_LEN 40 #define MAX_ZONEID_LEN 16 -/* characters not allowed in hostnames */ -#define HOSTNAME_INVALID_CHARS " \r\n\t/:#?!@{}[]\\$\'\"^`*<>=;,+&()%|" - /* * If USE_IPV6 is disabled, we still want to parse IPv6 addresses, so make * sure we have _some_ value for AF_INET6 without polluting our fake value @@ -148,17 +145,21 @@ UNITTEST CURLUcode urlencode_str(struct dynbuf *o, const char *url, len -= n; } - for(iptr = host_sep; len && !result; iptr++, len--) { + for(iptr = host_sep; len && !result;) { if(*iptr == ' ') { if(query != QUERY_YES) result = curlx_dyn_addn(o, "%20", 3); else result = curlx_dyn_addn(o, "+", 1); + iptr++; + len--; } else if((*iptr < ' ') || (*iptr >= 0x7f)) { unsigned char out[3] = { '%' }; Curl_hexbyte(&out[1], *iptr); result = curlx_dyn_addn(o, out, 3); + iptr++; + len--; } else if(*iptr == '%' && (len >= 3) && ISXDIGIT(iptr[1]) && ISXDIGIT(iptr[2]) && @@ -169,13 +170,30 @@ UNITTEST CURLUcode urlencode_str(struct dynbuf *o, const char *url, unsigned char out[3] = { '%' }; Curl_hexbyte(&out[1], hex); result = curlx_dyn_addn(o, out, 3); - iptr += 2; - len -= 2; + iptr += 3; + len -= 3; } else { - result = curlx_dyn_addn(o, iptr, 1); - if(*iptr == '?' && (query == QUERY_NOT_YET)) - query = QUERY_YES; + const unsigned char *start = iptr; + while(len) { + if(*iptr == ' ' || *iptr < ' ' || *iptr >= 0x7f) + break; + if(*iptr == '%' && (len >= 3) && + ISXDIGIT(iptr[1]) && ISXDIGIT(iptr[2]) && + (ISLOWER(iptr[1]) || ISLOWER(iptr[2]))) + break; + if(*iptr == '?') { + if(query == QUERY_NOT_YET) { + iptr++; + len--; + query = QUERY_YES; + break; + } + } + iptr++; + len--; + } + result = curlx_dyn_addn(o, (const char *)start, (size_t)(iptr - start)); } } @@ -204,18 +222,20 @@ size_t Curl_is_absolute_url(const char *url, char *buf, size_t buflen, if(guess_scheme && STARTS_WITH_DRIVE_PREFIX(url)) return 0; #endif - if(ISALPHA(url[0])) + if(ISALPHA(url[0])) { + if(buf) + buf[0] = Curl_raw_tolower(url[0]); for(i = 1; i < MAX_SCHEME_LEN; ++i) { char s = url[i]; if(s && (ISALNUM(s) || (s == '+') || (s == '-') || (s == '.'))) { - /* RFC 3986 3.1 explains: - scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." ) - */ + if(buf) + buf[i] = Curl_raw_tolower(s); } else { break; } } + } if(i && (url[i] == ':') && ((url[i + 1] == '/') || !guess_scheme)) { /* If this does not guess scheme, the scheme always ends with the colon so that this also detects data: URLs etc. In guessing mode, data: could @@ -223,32 +243,26 @@ size_t Curl_is_absolute_url(const char *url, char *buf, size_t buflen, /* the length of the scheme is the name part only */ size_t len = i; - if(buf) { - Curl_strntolower(buf, url, i); + if(buf) buf[i] = 0; - } return len; } + if(buf) + buf[0] = 0; return 0; } -/* scan for byte values <= 31, 127 and sometimes space */ -CURLUcode Curl_junkscan(const char *url, size_t *urllen, bool allowspace) +/* scan for byte values <= 31, 127 and maybe space */ +static bool badoctets(const char *input, size_t n, int flags) { - size_t n = strlen(url); - size_t i; - unsigned char control; - const unsigned char *p = (const unsigned char *)url; - if(n > CURL_MAX_INPUT_LENGTH) - return CURLUE_MALFORMED_INPUT; - - control = allowspace ? 0x1f : 0x20; - for(i = 0; i < n; i++) { - if(p[i] <= control || p[i] == 127) - return CURLUE_MALFORMED_INPUT; + const uint8_t *p = (const unsigned char *)input; + const uint8_t control = flags & CURLU_ALLOW_SPACE ? 0x1f : 0x20; + while(n--) { + if(*p <= control || *p == 127) + return TRUE; + p++; } - *urllen = n; - return CURLUE_OK; + return FALSE; } /* @@ -336,9 +350,20 @@ UNITTEST CURLUcode parse_hostname_login(struct Curl_URL *u, u->options = optionsp; } - /* the hostname starts at this offset */ - *hostname_offset = ptr - login; - return CURLUE_OK; + if(userp && badoctets(userp, strlen(userp), flags)) + ures = CURLUE_BAD_USER; + else if(passwdp && badoctets(passwdp, strlen(passwdp), flags)) + ures = CURLUE_BAD_PASSWORD; + else if(optionsp && badoctets(optionsp, strlen(optionsp), flags)) + ures = CURLUE_MALFORMED_INPUT; + + userp = passwdp = optionsp = NULL; + + if(!ures) { + /* the hostname starts at this offset */ + *hostname_offset = ptr - login; + return CURLUE_OK; + } out: @@ -368,7 +393,7 @@ UNITTEST CURLUcode parse_port(struct Curl_URL *u, struct dynbuf *host, u->portnum = 0; u->port_present = FALSE; if(hostname[0] == '[') { - portptr = strchr(hostname, ']'); + portptr = memchr(hostname + 1, ']', curlx_dyn_len(host) - 1); if(!portptr) return CURLUE_BAD_IPV6; portptr++; @@ -381,7 +406,7 @@ UNITTEST CURLUcode parse_port(struct Curl_URL *u, struct dynbuf *host, portptr = NULL; } else - portptr = strchr(hostname, ':'); + portptr = memchr(hostname, ':', curlx_dyn_len(host)); if(portptr) { curl_off_t port; @@ -476,31 +501,50 @@ UNITTEST CURLUcode ipv6_parse(struct Curl_URL *u, char *hostname, return CURLUE_OK; } -static CURLUcode hostname_check(struct Curl_URL *u, char *hostname, +/* characters not allowed in hostnames: + " \r\n\t/:#?!@{}[]\\$\'\"^`*<>=;,+&()%|" */ + +static const bool invalid_host_char[256] = { + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 0x00-0x0F */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 0x10-0x1F */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, /* 0x20-0x2F */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, /* 0x30-0x3F */ + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x40-0x4F */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, /* 0x50-0x5F */ + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x60-0x6F */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1 /* 0x70-0x7F */ +}; + +/* the input is a confirmed hostname, never an IPv6 address */ +static CURLUcode hostname_check(char *hostname, size_t hlen) +{ + size_t i; + for(i = 0; i < hlen; i++) { + if(invalid_host_char[(unsigned char)hostname[i]]) + return CURLUE_BAD_HOSTNAME; + } + if((hlen >= 2) && + (hostname[hlen - 1] == '.') && (hostname[hlen - 2] == '.')) + /* more than one trailing dot is not allowed */ + return CURLUE_BAD_HOSTNAME; + else if((hlen == 1) && (hostname[0] == '.')) + /* a single dot alone is not allowed */ + return CURLUE_BAD_HOSTNAME; + return CURLUE_OK; +} + +/* the input is a hostname or perhaps an IPv6 address */ +static CURLUcode hostname_check6(struct Curl_URL *u, char *hostname, size_t hlen) /* length of hostname */ { - size_t len; DEBUGASSERT(hostname); if(!hlen) return CURLUE_NO_HOST; else if(hostname[0] == '[') return ipv6_parse(u, hostname, hlen); - else { - /* letters from the second string are not ok */ - len = strcspn(hostname, HOSTNAME_INVALID_CHARS); - if(hlen != len) - /* hostname with bad content */ - return CURLUE_BAD_HOSTNAME; - else if((hlen >= 2) && - (hostname[hlen - 1] == '.') && (hostname[hlen - 2] == '.')) - /* more than one trailing dot is not allowed */ - return CURLUE_BAD_HOSTNAME; - else if((hlen == 1) && (hostname[0] == '.')) - /* a single dot alone is not allowed */ - return CURLUE_BAD_HOSTNAME; - } - return CURLUE_OK; + + return hostname_check(hostname, hlen); } /* @@ -529,14 +573,14 @@ UNITTEST int ipv4_normalize(struct dynbuf *host) unsigned int parts[4] = { 0, 0, 0, 0 }; CURLcode result = CURLE_OK; - if(*c == '[') - return HOST_IPV6; + if(!ISDIGIT(*c)) + return HOST_NAME; while(!done) { int rc; curl_off_t l; if(*c == '0') { - if(Curl_raw_tolower(c[1]) == 'x') { + if((c[1] | 0x20) == 'x') { c += 2; /* skip the prefix */ rc = curlx_str_hex(&c, &l, UINT_MAX); if(rc) @@ -631,7 +675,7 @@ static CURLUcode urldecode_host(struct dynbuf *host) { const char *per; const char *hostname = curlx_dyn_ptr(host); - per = strchr(hostname, '%'); + per = memchr(hostname, '%', curlx_dyn_len(host)); if(!per) /* nothing to decode */ return CURLUE_OK; @@ -676,6 +720,7 @@ static CURLUcode parse_authority(struct Curl_URL *u, return uc; } + /* parse_port() also sets the hostname length correctly */ uc = parse_port(u, host, has_scheme); if(!curlx_dyn_len(host)) @@ -684,23 +729,17 @@ static CURLUcode parse_authority(struct Curl_URL *u, if(!uc) uc = urldecode_host(host); if(uc) - return uc; - - switch(ipv4_normalize(host)) { - case HOST_IPV4: - break; - case HOST_IPV6: + ; + else if(auth[offset] == '[') uc = ipv6_parse(u, curlx_dyn_ptr(host), curlx_dyn_len(host)); - break; - case HOST_NAME: - uc = hostname_check(u, curlx_dyn_ptr(host), curlx_dyn_len(host)); - break; - case HOST_ERROR: - uc = CURLUE_OUT_OF_MEMORY; - break; - default: - uc = CURLUE_BAD_HOSTNAME; /* Bad IPv4 address even */ - break; + else { + /* ipv4_normalize() returns *NAME, *IPV4 or *ERROR */ + int type = ipv4_normalize(host); + + if(type == HOST_NAME) + uc = hostname_check(curlx_dyn_ptr(host), curlx_dyn_len(host)); + else if(type == HOST_ERROR) + uc = CURLUE_OUT_OF_MEMORY; } return uc; @@ -756,6 +795,8 @@ static bool needs_dedotdot(const char *p, size_t pn) /* a single byte path cannot be cleaned up */ if(pn < 2) return FALSE; + if(!memchr(p, '.', pn) && !memchr(p, '%', pn)) + return FALSE; while(pn) { if(is_dot(&p, &pn)) { /* "./" or dot before end of string */ @@ -999,7 +1040,8 @@ static CURLUcode parse_scheme(const char *url, CURLU *u, char *schemebuf, if(schemelen) { int num_slashes = 0; const char *p = &url[schemelen + 1]; - if(!Curl_get_scheme(schemebuf) && !(flags & CURLU_NON_SUPPORT_SCHEME)) + if(!Curl_getn_scheme(schemebuf, schemelen) && + !(flags & CURLU_NON_SUPPORT_SCHEME)) return CURLUE_UNSUPPORTED_SCHEME; if(!ISSLASH(*p)) @@ -1090,6 +1132,8 @@ static CURLUcode handle_fragment(CURLU *u, const char *fragment, u->fragment = curlx_dyn_ptr(&enc); } else { + if(badoctets(fragment, fraglen, flags)) + return CURLUE_BAD_FRAGMENT; u->fragment = curlx_memdup0(fragment + 1, fraglen - 1); if(!u->fragment) return CURLUE_OUT_OF_MEMORY; @@ -1114,6 +1158,9 @@ static CURLUcode handle_query(CURLU *u, const char *query, u->query = curlx_dyn_ptr(&enc); } else { + if(badoctets(query, qlen, flags)) + return CURLUE_BAD_QUERY; + u->query = curlx_memdup0(query + 1, qlen - 1); if(!u->query) return CURLUE_OUT_OF_MEMORY; @@ -1144,6 +1191,9 @@ static CURLUcode handle_path(CURLU *u, const char *path, } if(pathlen >= (size_t)(1 + !is_file)) { + if(badoctets(path, pathlen, flags)) + return CURLUE_BAD_PATH; + /* paths for file:// scheme can be one byte, others need to be two */ if(!u->path) { u->path = curlx_memdup0(path, pathlen); @@ -1183,31 +1233,35 @@ static CURLUcode parseurl(const char *url, CURLU *u, unsigned int flags) DEBUGASSERT(url); - curlx_dyn_init(&host, CURL_MAX_INPUT_LENGTH); + urllen = strlen(url); + if(urllen > CURL_MAX_INPUT_LENGTH) + return CURLUE_MALFORMED_INPUT; - ures = Curl_junkscan(url, &urllen, !!(flags & CURLU_ALLOW_SPACE)); - if(ures) - goto fail; + curlx_dyn_init(&host, CURL_MAX_INPUT_LENGTH); schemelen = Curl_is_absolute_url(url, schemebuf, sizeof(schemebuf), flags & (CURLU_GUESS_SCHEME | CURLU_DEFAULT_SCHEME)); /* handle the file: scheme */ - if(schemelen && !strcmp(schemebuf, "file")) { + if(schemelen == 4 && !memcmp(schemebuf, "file", 4)) { is_file = TRUE; ures = parse_file(url, urllen, u, &path, &pathlen); } else { const char *hostp = NULL; + const char *p; size_t hostlen; ures = parse_scheme(url, u, schemebuf, schemelen, flags, &hostp); if(ures) goto fail; /* find the end of the hostname + port number */ - hostlen = strcspn(hostp, "/?#"); - path = &hostp[hostlen]; + p = hostp; + while(*p && *p != '/' && *p != '?' && *p != '#') + p++; + hostlen = p - hostp; + path = p; /* this pathlen also contains the query and the fragment */ pathlen = urllen - (path - url); @@ -1227,7 +1281,7 @@ static CURLUcode parseurl(const char *url, CURLU *u, unsigned int flags) if(!ures) { /* The path might at this point contain a fragment and/or a query to handle */ - const char *fragment = strchr(path, '#'); + const char *fragment = memchr(path, '#', pathlen); if(fragment) { size_t fraglen = pathlen - (fragment - path); ures = handle_fragment(u, fragment, fraglen, flags); @@ -1999,11 +2053,11 @@ static CURLUcode url_sethost(CURLU *u, struct dynbuf *encp, size_t dlen; char *decoded = NULL; CURLcode result = Curl_urldecode(newp, n, &decoded, &dlen, REJECT_CTRL); - if(result || hostname_check(u, decoded, dlen)) + if(result || hostname_check6(u, decoded, dlen)) bad = TRUE; curlx_free(decoded); } - else if(hostname_check(u, newp, n)) + else if(hostname_check6(u, newp, n)) bad = TRUE; if(bad) { curlx_dyn_free(encp); diff --git a/lib/vdns/doh.c b/lib/vdns/doh.c index caf058f5161f..57d8826a5a3d 100644 --- a/lib/vdns/doh.c +++ b/lib/vdns/doh.c @@ -946,6 +946,18 @@ static CURLcode doh_decode_rdata_name(const unsigned char **buf, return CURLE_OK; } +/* scan for byte values <= 32 or 127 */ +static CURLcode junkscan(const char *url) +{ + const unsigned char *p = (const unsigned char *)url; + while(*p) { + if(*p <= 0x20 || *p == 127) + return CURLE_WEIRD_SERVER_REPLY; + p++; + } + return CURLE_OK; +} + /* @unittest 1658 */ UNITTEST CURLcode doh_resp_decode_httpsrr(struct Curl_easy *data, const unsigned char *cp, size_t len, @@ -959,7 +971,6 @@ UNITTEST CURLcode doh_resp_decode_httpsrr(struct Curl_easy *data, struct Curl_https_rrinfo *lhrr = NULL; char *dnsname = NULL; CURLcode result = CURLE_OUT_OF_MEMORY; - size_t olen; (void)data; *hrr = NULL; @@ -974,9 +985,9 @@ UNITTEST CURLcode doh_resp_decode_httpsrr(struct Curl_easy *data, if(doh_decode_rdata_name(&cp, &len, &dnsname) != CURLE_OK) goto err; lhrr->target = dnsname; - if(Curl_junkscan(dnsname, &olen, FALSE)) { + result = junkscan(dnsname); + if(result) { /* unacceptable hostname content */ - result = CURLE_WEIRD_SERVER_REPLY; goto err; } while(len >= 4) { diff --git a/tests/libtest/lib1560.c b/tests/libtest/lib1560.c index 083738898f23..1c31d3b38d4e 100644 --- a/tests/libtest/lib1560.c +++ b/tests/libtest/lib1560.c @@ -194,23 +194,27 @@ static const struct testcase get_parts_list[] = { "https | [11] | [12] | [13] | [::13.1.68.3] | 1234 " "| / | [16] | [17]", CURLU_DEFAULT_SCHEME, 0, CURLUE_OK}, { /* query and fragments with control characters */ - "http://host/path/?\001#\002", - "", CURLU_URLENCODE, 0, CURLUE_MALFORMED_INPUT }, + "http://host/path/?a\001#b\002", + "http | [11] | [12] | [13] | host | [15] | /path/ | a%01 | b%02", + CURLU_URLENCODE, 0, CURLUE_OK }, { /* Control code in port number is never okay, even when URL encoding */ "http://host:\x1f""80/path/", - "", CURLU_URLENCODE, 0, CURLUE_MALFORMED_INPUT }, + "", CURLU_URLENCODE, 0, CURLUE_BAD_PORT_NUMBER }, { /* Control code in scheme never okay, even when URL encoding */ "http\x1f://host/path/", - "", CURLU_URLENCODE, 0, CURLUE_MALFORMED_INPUT }, + "", CURLU_URLENCODE, 0, CURLUE_BAD_SCHEME }, { /* Control code in separator never okay, even when URL encoding */ "http:/%1f/host/path/", "", CURLU_URLENCODE, 0, CURLUE_BAD_HOSTNAME }, { /* Path with control characters */ "http://host/path\001with\002ctrl/", - "", CURLU_URLENCODE, 0, CURLUE_MALFORMED_INPUT }, + "http | [11] | [12] | [13] | host | [15] | /path%01with%02ctrl/ | [16] |" + " [17]", + CURLU_URLENCODE, 0, CURLUE_OK }, { /* Path with control characters and space! */ "http://host/path\x7fwith space/", - "", CURLU_URLENCODE, 0, CURLUE_MALFORMED_INPUT }, + "http | [11] | [12] | [13] | host | [15] | /path%7Fwith%20space/ | [16] " + "| [17]", CURLU_URLENCODE, 0, CURLUE_OK }, { /* Ensure %2f is NOT normalized to / even if path-as-is is NOT set */ "http://host/path%2f..%2f/", "http | [11] | [12] | [13] | host | [15] | /path%2f..%2f/ | [16] | [17]", @@ -281,7 +285,7 @@ static const struct testcase get_parts_list[] = { "https | [11] | [12] | [13] | curl.se | [15] | / | [16] | %20%20", CURLU_URLENCODE | CURLU_ALLOW_SPACE, 0, CURLUE_OK }, { "", "", 0, 0, CURLUE_MALFORMED_INPUT }, - { " ", "", 0, 0, CURLUE_MALFORMED_INPUT }, + { " ", "", 0, 0, CURLUE_BAD_SCHEME }, { "1h://example.net", "", 0, 0, CURLUE_BAD_SCHEME }, { "..://example.net", "", 0, 0, CURLUE_BAD_SCHEME }, { "-ht://example.net", "", 0, 0, CURLUE_BAD_SCHEME }, @@ -413,15 +417,15 @@ static const struct testcase get_parts_list[] = { #endif {"https://user:password@example.net/get?this=and#but frag then", "", - CURLU_DEFAULT_SCHEME, 0, CURLUE_MALFORMED_INPUT}, + CURLU_DEFAULT_SCHEME, 0, CURLUE_BAD_FRAGMENT}, {"https://user:password@example.net/get?this=and what", "", - CURLU_DEFAULT_SCHEME, 0, CURLUE_MALFORMED_INPUT}, + CURLU_DEFAULT_SCHEME, 0, CURLUE_BAD_QUERY}, {"https://user:password@example.net/ge t?this=and-what", "", - CURLU_DEFAULT_SCHEME, 0, CURLUE_MALFORMED_INPUT}, + CURLU_DEFAULT_SCHEME, 0, CURLUE_BAD_PATH}, {"https://user:pass word@example.net/get?this=and-what", "", - CURLU_DEFAULT_SCHEME, 0, CURLUE_MALFORMED_INPUT}, + CURLU_DEFAULT_SCHEME, 0, CURLUE_BAD_PASSWORD}, {"https://u ser:password@example.net/get?this=and-what", "", - CURLU_DEFAULT_SCHEME, 0, CURLUE_MALFORMED_INPUT}, + CURLU_DEFAULT_SCHEME, 0, CURLUE_BAD_USER}, {"imap://user:pass;opt ion@server/path", "", CURLU_DEFAULT_SCHEME, 0, CURLUE_MALFORMED_INPUT}, /* no space allowed in scheme */ @@ -644,9 +648,9 @@ static const struct testcase get_parts_list[] = { "https | [11] | [12] | [13] | 127abc.com | [15] | / | [16] | [17]", CURLU_DEFAULT_SCHEME, 0, CURLUE_OK}, {"https:// example.com?check", "", - CURLU_DEFAULT_SCHEME, 0, CURLUE_MALFORMED_INPUT}, + CURLU_DEFAULT_SCHEME, 0, CURLUE_BAD_HOSTNAME}, {"https://e x a m p l e.com?check", "", - CURLU_DEFAULT_SCHEME, 0, CURLUE_MALFORMED_INPUT}, + CURLU_DEFAULT_SCHEME, 0, CURLUE_BAD_HOSTNAME}, {"https://example.com?check", "https | [11] | [12] | [13] | example.com | [15] | / | check | [17]", CURLU_DEFAULT_SCHEME, 0, CURLUE_OK}, @@ -732,6 +736,10 @@ static const struct urltestcase get_url_list[] = { {"https://[fe80%3A%3A20c%3A29ff%3Afe9c%3A409b]/", "https://[fe80::20c:29ff:fe9c:409b]/", 0, 0, CURLUE_OK }, + /* URL encoded [brackets] */ + {"https://%5bfe80%3A%3A20c%3A29ff%3Afe9c%3A409b%5c/", + "", 0, 0, CURLUE_BAD_HOSTNAME }, + /* IPvFuture format */ {"http://[v1.fe80::abcd]/", "", 0, 0, CURLUE_BAD_IPV6}, @@ -929,7 +937,7 @@ static const struct urltestcase get_url_list[] = { {"https://\xff.127.0.0.1", "https://%FF.127.0.0.1/", 0, CURLU_URLENCODE, CURLUE_OK}, {"https://127.-0.0.1", "https://127.-0.0.1/", 0, 0, CURLUE_OK}, - {"https://127.0. 1", "https://127.0.0.1/", 0, 0, CURLUE_MALFORMED_INPUT}, + {"https://127.0. 1", "https://127.0.0.1/", 0, 0, CURLUE_BAD_HOSTNAME}, {"https://1.2.3.256", "https://1.2.3.256/", 0, 0, CURLUE_OK}, {"https://1.2.3.256.", "https://1.2.3.256./", 0, 0, CURLUE_OK}, {"https://1.2.3.4.5", "https://1.2.3.4.5/", 0, 0, CURLUE_OK}, @@ -1024,14 +1032,18 @@ static const struct urltestcase get_url_list[] = { CURLU_GUESS_SCHEME, 0, CURLUE_OK}, {"HTTP://test/", "http://test/", 0, 0, CURLUE_OK}, {"http://HO0_-st..~./", "http://HO0_-st..~./", 0, 0, CURLUE_OK}, - {"http:/@example.com: 123/", "", 0, 0, CURLUE_MALFORMED_INPUT}, - {"http:/@example.com:123 /", "", 0, 0, CURLUE_MALFORMED_INPUT}, + {"http:/@example.com: 123/", "", 0, 0, CURLUE_BAD_PORT_NUMBER}, + {"http:/@example.com:123 /", "", 0, 0, CURLUE_BAD_PORT_NUMBER}, {"http:/@example.com:123a/", "", 0, 0, CURLUE_BAD_PORT_NUMBER}, - {"http://host/file\r", "", 0, 0, CURLUE_MALFORMED_INPUT}, - {"http://host/file\n\x03", "", 0, 0, CURLUE_MALFORMED_INPUT}, + {"http://host/file\r", "", 0, 0, CURLUE_BAD_PATH}, + {"http://host/file?\r", "", 0, 0, CURLUE_BAD_QUERY}, + {"http://host/file?hello#\r", "", 0, 0, CURLUE_BAD_FRAGMENT}, + {"http://\r:hej@host/file", "", 0, 0, CURLUE_BAD_USER}, + {"http://hej:\r@host/file", "", 0, 0, CURLUE_BAD_PASSWORD}, + {"http://host/file\n\x03", "", 0, 0, CURLUE_BAD_PATH}, {"htt\x02://host/file", "", - CURLU_NON_SUPPORT_SCHEME, 0, CURLUE_MALFORMED_INPUT}, - {" http://host/file", "", 0, 0, CURLUE_MALFORMED_INPUT}, + CURLU_NON_SUPPORT_SCHEME, 0, CURLUE_BAD_SCHEME}, + {" http://host/file", "", 0, 0, CURLUE_BAD_SCHEME}, /* here the password ends at the semicolon and options is 'word' */ {"imap://user:pass;word@host/file", "imap://user:pass;word@host/file", From 10c5f8e72238e94e62d6d35a6ebd34ddb5bca1df Mon Sep 17 00:00:00 2001 From: Daniel Stenberg Date: Sun, 13 Sep 2026 00:41:49 +0200 Subject: [PATCH 121/481] test 1605: move from unit to libtest It only uses public libcurl functions. Closes #22928 --- tests/data/test1605 | 8 ++++---- tests/libtest/Makefile.inc | 1 + tests/{unit/unit1605.c => libtest/lib1605.c} | 2 +- tests/unit/Makefile.inc | 1 - 4 files changed, 6 insertions(+), 6 deletions(-) rename tests/{unit/unit1605.c => libtest/lib1605.c} (97%) diff --git a/tests/data/test1605 b/tests/data/test1605 index 81431adb733c..e8edfb2bdca8 100644 --- a/tests/data/test1605 +++ b/tests/data/test1605 @@ -2,15 +2,15 @@ -unittest +curl_easy_escape # Client-side - -unittest - + +lib%TESTNUMBER + Test negative data lengths as input to libcurl functions diff --git a/tests/libtest/Makefile.inc b/tests/libtest/Makefile.inc index b2829a8172b2..1c5bd6633019 100644 --- a/tests/libtest/Makefile.inc +++ b/tests/libtest/Makefile.inc @@ -236,6 +236,7 @@ TESTS_C = \ lib1597.c \ lib1598.c \ lib1599.c \ + lib1605.c \ lib1647.c \ lib1648.c \ lib1649.c \ diff --git a/tests/unit/unit1605.c b/tests/libtest/lib1605.c similarity index 97% rename from tests/unit/unit1605.c rename to tests/libtest/lib1605.c index baeb121085ef..da8109cc711a 100644 --- a/tests/unit/unit1605.c +++ b/tests/libtest/lib1605.c @@ -43,7 +43,7 @@ static void t1605_stop(CURL *easy) curl_global_cleanup(); } -static CURLcode test_unit1605(const char *arg) +static CURLcode test_lib1605(const char *arg) { CURL *easy; diff --git a/tests/unit/Makefile.inc b/tests/unit/Makefile.inc index c393c74e33e8..0f51a3bc802c 100644 --- a/tests/unit/Makefile.inc +++ b/tests/unit/Makefile.inc @@ -46,7 +46,6 @@ TESTS_C = \ unit1601.c \ unit1602.c \ unit1603.c \ - unit1605.c \ unit1606.c \ unit1607.c \ unit1608.c \ From d7546809d5bd548b69ec49fca26565c8f6f0d344 Mon Sep 17 00:00:00 2001 From: Daniel Stenberg Date: Sun, 13 Sep 2026 00:06:44 +0200 Subject: [PATCH 122/481] mime: do curl_mime_free() non-recursively Verify with test 3595: deeply nested curl_mime_free() Closes #22927 --- lib/mime.c | 28 +++++++++++---- tests/data/Makefile.am | 2 +- tests/data/test3595 | 21 ++++++++++++ tests/libtest/Makefile.inc | 1 + tests/libtest/lib3595.c | 70 ++++++++++++++++++++++++++++++++++++++ 5 files changed, 115 insertions(+), 7 deletions(-) create mode 100644 tests/data/test3595 create mode 100644 tests/libtest/lib3595.c diff --git a/lib/mime.c b/lib/mime.c index 635eb310ad20..3522679a16e2 100644 --- a/lib/mime.c +++ b/lib/mime.c @@ -1109,20 +1109,36 @@ void Curl_mime_cleanpart(curl_mimepart *part) } } -/* Recursively delete a mime handle and its parts. */ +/* Non-recursively delete a mime handle and its parts. */ void curl_mime_free(curl_mime *mime) { curl_mimepart *part; - if(mime) { - mime_subparts_unbind(mime); /* Be sure it is not referenced anymore. */ - while(mime->firstpart) { - part = mime->firstpart; + if(!mime) + return; + + mime_subparts_unbind(mime); /* Be sure it is not referenced anymore. */ + + while(mime) { + part = mime->firstpart; + if(part) { mime->firstpart = part->nextpart; + if(part->kind == MIMEKIND_MULTIPART && part->arg && + part->freefunc == mime_subparts_free) { + curl_mime *subparts = (curl_mime *)part->arg; + part->freefunc = NULL; + cleanup_part_content(part); + subparts->parent = mime->parent; + mime->parent = (curl_mimepart *)subparts; + } Curl_mime_cleanpart(part); curlx_free(part); } - curlx_free(mime); + else { + curl_mime *parent = (curl_mime *)mime->parent; + curlx_free(mime); + mime = parent; + } } } diff --git a/tests/data/Makefile.am b/tests/data/Makefile.am index 8577e0226a22..9f70f3d260cb 100644 --- a/tests/data/Makefile.am +++ b/tests/data/Makefile.am @@ -2100,7 +2100,7 @@ TESTCASES = \ test3401 \ \ test3500 \ - \ + test3595 \ test4000 \ test4001 \ test4644 \ diff --git a/tests/data/test3595 b/tests/data/test3595 new file mode 100644 index 000000000000..b2b1dd26c21d --- /dev/null +++ b/tests/data/test3595 @@ -0,0 +1,21 @@ + + + + +mime + + + +# Client-side + + +lib%TESTNUMBER + + +curl_mime_free + + + + + + diff --git a/tests/libtest/Makefile.inc b/tests/libtest/Makefile.inc index 1c5bd6633019..bf13551b5b2a 100644 --- a/tests/libtest/Makefile.inc +++ b/tests/libtest/Makefile.inc @@ -331,5 +331,6 @@ TESTS_C = \ lib3207.c \ lib3208.c \ lib3231.c \ + lib3595.c \ lib5000.c \ lib5004.c diff --git a/tests/libtest/lib3595.c b/tests/libtest/lib3595.c new file mode 100644 index 000000000000..1f343f503eef --- /dev/null +++ b/tests/libtest/lib3595.c @@ -0,0 +1,70 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ +#include "unitcheck.h" + +static CURLcode test_lib3595(const char *arg) +{ + UNITTEST_BEGIN_SIMPLE + +#if !defined(CURL_DISABLE_MIME) && \ + (!defined(CURL_DISABLE_HTTP) || \ + !defined(CURL_DISABLE_SMTP) || \ + !defined(CURL_DISABLE_IMAP)) + + curl_mime *root = NULL; + curl_mime *current = NULL; + curl_mime *sub = NULL; + curl_mimepart *part = NULL; + int i; + + if(curl_global_init(CURL_GLOBAL_ALL) != CURLE_OK) { + curl_mfprintf(stderr, "curl_global_init() failed\n"); + return TEST_ERR_MAJOR_BAD; + } + + /* Create a deeply nested mime structure to verify that non-recursive + curl_mime_free works properly. */ + (void)arg; + root = curl_mime_init(NULL); + fail_unless(root != NULL, "curl_mime_init root failed"); + current = root; + + for(i = 0; i < 50; i++) { + part = curl_mime_addpart(current); + fail_unless(part != NULL, "curl_mime_addpart failed"); + sub = curl_mime_init(NULL); + fail_unless(sub != NULL, "curl_mime_init sub failed"); + curl_mime_subparts(part, sub); + current = sub; + } + + /* Free deep structure */ + curl_mime_free(root); + curl_global_cleanup(); +#else + (void)arg; +#endif + + UNITTEST_END_SIMPLE +} From 72dcca5ce9ccd59d22a92b48601be0e6d305a9e9 Mon Sep 17 00:00:00 2001 From: Daniel Stenberg Date: Sat, 12 Sep 2026 23:50:26 +0200 Subject: [PATCH 123/481] curl_formadd.md: mention the strlen() for names as well Clarify that this goes the same for the name as for the content. Closes #22926 --- docs/libcurl/curl_formadd.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/libcurl/curl_formadd.md b/docs/libcurl/curl_formadd.md index 354957bfbec6..d98469578f98 100644 --- a/docs/libcurl/curl_formadd.md +++ b/docs/libcurl/curl_formadd.md @@ -118,6 +118,10 @@ followed by a long giving the length of the name. Pass this option to set the length of *CURLFORM_COPYNAME* and *CURLFORM_PTRNAME* strings, if they are not null-terminated. +If you pass a 0 (zero) for this option, libcurl calls strlen() on the name to +figure out the size. If you really want to send a zero byte name then you must +make sure strlen() on the name pointer returns zero. + ## CURLFORM_FILECONTENT followed by a filename, causes that file to be read and its contents used From 6ae66b31d93ee7c28d6a798f81f041a98b85636b Mon Sep 17 00:00:00 2001 From: Daniel Stenberg Date: Mon, 14 Sep 2026 00:09:03 +0200 Subject: [PATCH 124/481] urlapi: reject spaces and control characters in zone id Follow-up to 0b04700029149d740f Extend test lib1560 to verify Closes #22936 --- lib/urlapi.c | 3 ++- tests/libtest/lib1560.c | 9 +++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/lib/urlapi.c b/lib/urlapi.c index 1b1e6fcafbac..339803321e76 100644 --- a/lib/urlapi.c +++ b/lib/urlapi.c @@ -470,7 +470,8 @@ UNITTEST CURLUcode ipv6_parse(struct Curl_URL *u, char *hostname, /* pass '25' if present and is a URL encoded percent sign */ if(!strncmp(h, "25", 2) && h[2] && (h[2] != ']')) h += 2; - while(*h && (*h != ']') && (i < (MAX_ZONEID_LEN - 1))) + while(*h && (*h != ']') && (i < (MAX_ZONEID_LEN - 1)) && + (*h != ' ')) zoneid[i++] = *h++; if(!i || (']' != *h)) return CURLUE_BAD_IPV6; diff --git a/tests/libtest/lib1560.c b/tests/libtest/lib1560.c index 1c31d3b38d4e..ace91e3a99c5 100644 --- a/tests/libtest/lib1560.c +++ b/tests/libtest/lib1560.c @@ -890,6 +890,15 @@ static const struct urltestcase get_url_list[] = { {"https://[::%25fakeit];80/moo", "", 0, 0, CURLUE_BAD_PORT_NUMBER}, + {"https://[::%25fa keit]:80/moo", + "", + 0, 0, CURLUE_BAD_IPV6}, + {"https://[::%25fa keit]:80/moo", + "", + CURLU_ALLOW_SPACE, 0, CURLUE_BAD_IPV6}, + {"https://[::%25fake\tit]:80/moo", + "", + 0, 0, CURLUE_BAD_HOSTNAME}, {"https://[fe80::20c:29ff:fe9c:409b]-80/moo", "", 0, 0, CURLUE_BAD_PORT_NUMBER}, From 4db25ff7e9813bad30d44b7134fbe7f444993aa0 Mon Sep 17 00:00:00 2001 From: Daniel Stenberg Date: Mon, 14 Sep 2026 08:47:11 +0200 Subject: [PATCH 125/481] RELEASE-NOTES: synced --- RELEASE-NOTES | 54 +++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 52 insertions(+), 2 deletions(-) diff --git a/RELEASE-NOTES b/RELEASE-NOTES index b326c359e738..dd043fcff817 100644 --- a/RELEASE-NOTES +++ b/RELEASE-NOTES @@ -4,54 +4,74 @@ curl and libcurl 8.22.1 Command line options: 278 curl_easy_setopt() options: 312 Public functions in libcurl: 100 - Authors: 1526 - Contributors: 3800 + Authors: 1529 + Contributors: 3804 This release includes the following changes: + o lib: CURL_GLOBAL_WIN32 -> CURL_GLOBAL_WINSOCK [64] + o SMB: drop support [63] o tool_cb_wrt: inject the no-clobber number before the extension [21] o tool_cb_wrt: try up to .9999 when no-clobbering [46] This release includes the following bugfixes: + o base64: optimize decode lookup table and quantum loop [88] o build: convert HAS_TIME_T_UNSIGNED to a compile-time check [65] o build: drop `HTTP_ONLY` option [23] o clang-tidy: merge nested `if()`s [39] o config2setopts: always set the security options for SCP/SFTP [67] + o conncache: avoid evicting connect-only connections [79] o conncache: remove bundle dest [50] o connectdata: use less curltime [34] o cookie: ignore individual cookie errors when loading [6] o cshutdown: make it smaller [60] o curl_addrinfo: drop redundant parentheses [41] o curl_fopen: restore the uid and gid checks [70] + o curl_formadd.md: mention the strlen() for names as well [29] o CURLOPT*ISSUERCERT*: not verified cryptographically [28] o CURLOPT_SHARE.md: remove bad explanation for cookie sharing [7] + o CURLOPT_SSL_CTX_FUNCTION.md: client cert caveats [87] + o cw-out: avoid O(n^2) work while buffering paused output [74] o digest: check peer equality for staleness [24] o digest: fix maximum length quoted value parsing [22] o digest: parse without storing 'value' in local buffer [20] + o dnscache/conncache tweaks [75] o docs: add the missing .html to the url-encode book link [3] o doh: reject undersized HTTPS RDATA [11] + o dynhds: grow header array geometrically [73] + o easy: duplicating a zero byte memory area needs no malloc [91] o easy: make ca_cache_timeout standalone [51] o escape: polish encoding and decoding for speed [37] + o fnmatch: replace recursive matcher with iterative greedy algorithm [72] o ftp: tighten use_ssl check for conn reuse [19] o ftp: URL encode file names when wildcarding [61] o getparam: switch off CONNECT headers when using -J [4] o global_init: tidy up, simplify, fix issues [43] + o happy eyeballs: bound work from many duplicate/failing addresses [80] o hash: change key arguments to const [14] + o headers: avoid O(n^2) rescans in curl_easy_nextheader() [81] o hsts: cap max-age to two years [47] o hsts: on load, only remove exact duplicates [56] o HTTP-CONNECT: do not react to 401 responses [31] o http2: changes in error handling [53] o http: reject resumed uploads [16] o imap: compare selected mailbox names case-sensitively [57] + o lib: connection shutdown, cleanup code [92] + o lib: make private functions static and remove unused code [83] o libssh2: match hashed known_hosts entries by host [30] + o libssh2: skip repeat checkp() for already-absent hashed key types [82] o mbedtls: fix ticket handling [52] + o mime: do curl_mime_free() non-recursively [48] o mprintf: stage output and emit it in runs [38] + o mulit_ev: remove socket references on forget [89] o multi: reduce struct size with small cleanups [25] o multi: remove redundant completion message list [32] o multi: store timeout epoch as whole seconds [36] o multi_ntfy: replace enabled bitset with inline flags [44] o multihandle: some easy shrinks [33] + o netrc: empty quoted values now produce a valid empty string [62] + o netrc: survive comment-only .netrc file [93] o ngtcp2: shrink max sendbuf [13] o openssl/gtls: reject CRLfile with native CA store, matching rustls [27] o openssl: use non-deprecated API for MD5/MD4 [69] @@ -61,12 +81,17 @@ This release includes the following bugfixes: o schannel: auto_client_cert conn matching [17] o schannel: clear borrowed sslContext on close [15] o ssl: dedup ssl_fsslctx [35] + o test 1605: move from unit to libtest [49] o test1399: improve reliability [9] o test1679: unset `SSL_CERT_FILE`, use `TESTNUMBER` [58] o tool: (re)set SSL verification in --libcurl output [1] + o tool_easysrc: reset the handle between --next operations [90] o uint-bset: skip unused slots when counting + o url: match connections without force reuse and candidates [95] o url: ntlm/negotiate, prohibit connection reuse on follows [18] + o urlapi: run the urlparser perf test faster [54] o urldata: remove conn->given [40] + o urldata: remove ssl_primary_config from easy handle [78] o vquic: fix preprocessor check for old Darwin versions [8] o vtls: fix Curl_cert_hostcheck when the pattern is not a C string [12] o warnless: dedupe Intel compiler warning suppression pragmas [42] @@ -128,6 +153,7 @@ References to bug reports and discussions on issues: [26] = https://curl.se/bug/?i=22822 [27] = https://curl.se/bug/?i=22826 [28] = https://curl.se/bug/?i=22825 + [29] = https://curl.se/bug/?i=22926 [30] = https://curl.se/bug/?i=22874 [31] = https://curl.se/bug/?i=22817 [32] = https://curl.se/bug/?i=22805 @@ -146,16 +172,40 @@ References to bug reports and discussions on issues: [45] = https://curl.se/bug/?i=22753 [46] = https://curl.se/bug/?i=22784 [47] = https://curl.se/bug/?i=22878 + [48] = https://curl.se/bug/?i=22927 + [49] = https://curl.se/bug/?i=22928 [50] = https://curl.se/bug/?i=22873 [51] = https://curl.se/bug/?i=22877 [52] = https://curl.se/bug/?i=22871 [53] = https://curl.se/bug/?i=22870 + [54] = https://curl.se/bug/?i=22924 [56] = https://curl.se/bug/?i=22869 [57] = https://curl.se/bug/?i=22868 [58] = https://curl.se/bug/?i=22866 [60] = https://curl.se/bug/?i=22862 [61] = https://curl.se/bug/?i=22860 + [62] = https://issues.oss-fuzz.com/issues/560352522 + [63] = https://curl.se/bug/?i=22652 + [64] = https://curl.se/bug/?i=22819 [65] = https://curl.se/bug/?i=22849 [67] = https://curl.se/bug/?i=22839 [69] = https://curl.se/bug/?i=22221 [70] = https://curl.se/bug/?i=22843 + [72] = https://curl.se/bug/?i=22911 + [73] = https://curl.se/bug/?i=22910 + [74] = https://curl.se/bug/?i=22896 + [75] = https://curl.se/bug/?i=22900 + [78] = https://curl.se/bug/?i=22901 + [79] = https://curl.se/bug/?i=22902 + [80] = https://curl.se/bug/?i=22897 + [81] = https://curl.se/bug/?i=22898 + [82] = https://curl.se/bug/?i=22905 + [83] = https://curl.se/bug/?i=22906 + [87] = https://curl.se/bug/?i=22899 + [88] = https://curl.se/bug/?i=22903 + [89] = https://curl.se/bug/?i=22895 + [90] = https://curl.se/bug/?i=22890 + [91] = https://curl.se/bug/?i=22893 + [92] = https://curl.se/bug/?i=22888 + [93] = https://curl.se/bug/?i=22889 + [95] = https://curl.se/bug/?i=22857 From 1e2d19f454b33443a0edd7910a46ad77f4367b4f Mon Sep 17 00:00:00 2001 From: Daniel Stenberg Date: Mon, 14 Sep 2026 10:09:26 +0200 Subject: [PATCH 126/481] test2924: CURLOPT_HTTP_TRANSFER_DECODING, chunked + Content-Length Verify correct treatment (ignore content length) Closes #22940 --- tests/data/Makefile.am | 1 + tests/data/test2924 | 54 ++++++++++++++++++++++++++++++++++++++ tests/libtest/Makefile.inc | 1 + tests/libtest/lib2924.c | 47 +++++++++++++++++++++++++++++++++ 4 files changed, 103 insertions(+) create mode 100644 tests/data/test2924 create mode 100644 tests/libtest/lib2924.c diff --git a/tests/data/Makefile.am b/tests/data/Makefile.am index 9f70f3d260cb..81cf24d6465a 100644 --- a/tests/data/Makefile.am +++ b/tests/data/Makefile.am @@ -2008,6 +2008,7 @@ TESTCASES = \ test2723 \ test2805 \ test2885 \ + test2924 \ test3000 \ test3001 \ test3002 \ diff --git a/tests/data/test2924 b/tests/data/test2924 new file mode 100644 index 000000000000..f56e35a835ec --- /dev/null +++ b/tests/data/test2924 @@ -0,0 +1,54 @@ + + + + +CURLOPT_HTTP_TRANSFER_DECODING +chunked Transfer-Encoding + + + +# Server-side + + +HTTP/1.1 200 OK +Date: Tue, 09 Nov 2010 14:49:00 GMT +Server: test-server/fake +Content-Length: 123456789 +Transfer-Encoding: chunked +Content-Type: text/html +Funny-head: yesyes + +10%CR +chunked data fun%CR +0%CR +%CR + + + +# Client-side + + +lib%TESTNUMBER + + + +http + + +CURLOPT_HTTP_TRANSFER_DECODING, chunked + Content-Length + + +http://%HOSTIP:%HTTPPORT/%TESTNUMBER + + + +# Verify data after the test has been "shot" + + +GET /%TESTNUMBER HTTP/1.1 +Host: %HOSTIP:%HTTPPORT +Accept: */* + + + + diff --git a/tests/libtest/Makefile.inc b/tests/libtest/Makefile.inc index bf13551b5b2a..d7651a34115d 100644 --- a/tests/libtest/Makefile.inc +++ b/tests/libtest/Makefile.inc @@ -316,6 +316,7 @@ TESTS_C = \ lib2506.c \ lib2700.c \ lib2805.c \ + lib2924.c \ lib3010.c \ lib3025.c \ lib3026.c \ diff --git a/tests/libtest/lib2924.c b/tests/libtest/lib2924.c new file mode 100644 index 000000000000..81ba4697e465 --- /dev/null +++ b/tests/libtest/lib2924.c @@ -0,0 +1,47 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ +#include "first.h" + +static CURLcode test_lib2924(const char *URL) +{ + CURL *curl; + CURLcode result = CURLE_OK; + + global_init(CURL_GLOBAL_ALL); + + easy_init(curl); + + easy_setopt(curl, CURLOPT_URL, URL); + easy_setopt(curl, CURLOPT_HEADER, 1L); + easy_setopt(curl, CURLOPT_HTTP_TRANSFER_DECODING, 0L); + + result = curl_easy_perform(curl); + +test_cleanup: + + curl_easy_cleanup(curl); + curl_global_cleanup(); + + return result; +} From 458704d484706732890599290c109d0149df5e52 Mon Sep 17 00:00:00 2001 From: Stephen Psaradellis Date: Sun, 13 Sep 2026 15:54:40 -0500 Subject: [PATCH 127/481] rtsp: accept a Content-Length body on methods that expected none pick_method() sets no_body for OPTIONS, ANNOUNCE, SETUP, PLAY, PAUSE, TEARDOWN, SET_PARAMETER and RECORD, so http_on_response() marked the transfer done at the end of the headers and a response body announced with Content-Length was thrown away as excess data. RFC 2326 allows a body on any RTSP response, so for RTSP only, when the response declares a Content-Length above zero, clear no_body and read it. Responses without a Content-Length, and all of HTTP, are unchanged. Adds test657 and removes the KNOWN_BUGS entry. Fixes #12414 Closes #22935 --- docs/KNOWN_BUGS.md | 10 -------- lib/http.c | 9 +++++++ tests/data/Makefile.am | 1 + tests/data/test657 | 58 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 68 insertions(+), 10 deletions(-) create mode 100644 tests/data/test657 diff --git a/docs/KNOWN_BUGS.md b/docs/KNOWN_BUGS.md index 6d98f26957d4..58e816cfc975 100644 --- a/docs/KNOWN_BUGS.md +++ b/docs/KNOWN_BUGS.md @@ -490,13 +490,3 @@ See https://curl.se/mail/lib-2024-05/0026.html and The transfer ends with error "QUIC connection is draining". [curl issue 12037](https://github.com/curl/curl/issues/12037) - -# RTSP - -## Some methods do not support response bodies - -The RTSP implementation is written to assume that a number of RTSP methods -always get responses without bodies, even though there seems to be no -indication in the RFC that this is always the case. - -[curl issue 12414](https://github.com/curl/curl/issues/12414) diff --git a/lib/http.c b/lib/http.c index 0ff7570089bc..7f76a9279f91 100644 --- a/lib/http.c +++ b/lib/http.c @@ -4210,6 +4210,15 @@ static CURLcode http_on_response(struct Curl_easy *data, if(result) goto out; +#ifndef CURL_DISABLE_RTSP + /* An RTSP response to any method may carry a body, which is announced + with Content-Length (RFC 2326 section 12.14). Read it instead of + treating it as excess data. */ + if((conn->scheme->protocol & CURLPROTO_RTSP) && data->req.no_body && + (k->size > 0)) + data->req.no_body = FALSE; +#endif + /* If we requested a "no body", this is a good time to get * out and return home. */ diff --git a/tests/data/Makefile.am b/tests/data/Makefile.am index 81cf24d6465a..723655b230b0 100644 --- a/tests/data/Makefile.am +++ b/tests/data/Makefile.am @@ -676,6 +676,7 @@ TESTCASES = \ test654 \ test655 \ test656 \ + test657 \ test658 \ test659 \ test660 \ diff --git a/tests/data/test657 b/tests/data/test657 new file mode 100644 index 000000000000..1bd24aae6538 --- /dev/null +++ b/tests/data/test657 @@ -0,0 +1,58 @@ + + + + +RTSP +OPTIONS + + + +# Server-side + + +RTSP/1.0 200 OK +Server: RTSPD/libcurl-test +CSeq: 1 +Public: DESCRIBE, OPTIONS, SETUP, TEARDOWN, PLAY, PAUSE +Content-Length: 18 + +No-More: sessions + + + +# Client-side + + +rtsp + + +lib567 + + +RTSP OPTIONS response with a body + + +rtsp://%HOSTIP:%RTSPPORT/%TESTNUMBER + + + +# Verify data after the test has been "shot" + + +OPTIONS rtsp://%HOSTIP:%RTSPPORT/%TESTNUMBER RTSP/1.0 +CSeq: 1 +User-Agent: test567 +Test-Number: 567 + + + +RTSP/1.0 200 OK +Server: RTSPD/libcurl-test +CSeq: 1 +Public: DESCRIBE, OPTIONS, SETUP, TEARDOWN, PLAY, PAUSE +Content-Length: 18 + +No-More: sessions + + + From 4a608405b6911e2fe374fca4239b3480a4e0cb2f Mon Sep 17 00:00:00 2001 From: Daniel Stenberg Date: Mon, 14 Sep 2026 11:56:48 +0200 Subject: [PATCH 128/481] test 3555: Basic TELNET negotiation over HTTP proxy Closes #22943 --- tests/data/Makefile.am | 2 +- tests/data/test3555 | 54 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+), 1 deletion(-) create mode 100644 tests/data/test3555 diff --git a/tests/data/Makefile.am b/tests/data/Makefile.am index 723655b230b0..7bc72f365741 100644 --- a/tests/data/Makefile.am +++ b/tests/data/Makefile.am @@ -2100,8 +2100,8 @@ TESTCASES = \ \ test3400 \ test3401 \ - \ test3500 \ + test3555 \ test3595 \ test4000 \ test4001 \ diff --git a/tests/data/test3555 b/tests/data/test3555 new file mode 100644 index 000000000000..f673ad6ca60b --- /dev/null +++ b/tests/data/test3555 @@ -0,0 +1,54 @@ + + + + +TELNET +UPLOAD +proxy + + + +# This is test 1452 done over HTTP proxy + + +HTTP/1.1 200 welcome +Date: Tue, 09 Nov 2010 14:49:00 GMT +Server: test-server/fake +Accept-Ranges: bytes +Content-Length: 0 +Connection: close + + + + + + +# Client-side + + +telnet +http-proxy + + +http +telnet +proxy + + +Basic TELNET negotiation over HTTP proxy + + +test%TESTNUMBER + + +telnet://%HOSTIP:%TELNETPORT --upload-file - -p -x http://%HOSTIP:%PROXYPORT/%TESTNUMBER + + + +# Verify data after the test has been "shot" + + +test%TESTNUMBER + + + From 753735af87f5c30cb6f60a70342f8bc4bc5fd5db Mon Sep 17 00:00:00 2001 From: Daniel Stenberg Date: Sun, 13 Sep 2026 16:12:01 +0200 Subject: [PATCH 129/481] pingpong: add a max loop counter for Curl_pp_readresp To make sure it cannot get stuck in this function. Reported-by: Joshua Rogers Closes #22932 --- lib/pingpong.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/pingpong.c b/lib/pingpong.c index b365772fb71a..b49f9c93ae0a 100644 --- a/lib/pingpong.c +++ b/lib/pingpong.c @@ -245,6 +245,7 @@ CURLcode Curl_pp_readresp(struct Curl_easy *data, CURLcode result = CURLE_OK; size_t gotbytes; char buffer[900]; + int maxloops = 10; *code = 0; /* 0 for errors or not done */ *size = 0; @@ -262,6 +263,9 @@ CURLcode Curl_pp_readresp(struct Curl_easy *data, pp->nfinal = 0; /* now gone */ } if(!pp->overflow) { + if(!maxloops--) /* don't get stuck */ + return CURLE_OK; + result = pingpong_read(data, sockindex, buffer, sizeof(buffer), &gotbytes); if(result == CURLE_AGAIN) From 3f79dc36253e88411e13188042909d9bdc1978db Mon Sep 17 00:00:00 2001 From: Stefan Eissing Date: Mon, 14 Sep 2026 10:11:47 +0200 Subject: [PATCH 130/481] telnet: make it work through proxy tunnels Change telnet's antiquated corner places where it still used the connection socket directly. Use the transfer send call instead which goes the proper way thought the connection filters. Closes #22941 --- lib/telnet.c | 55 ++++++++++++++++++++++++++-------------------------- 1 file changed, 28 insertions(+), 27 deletions(-) diff --git a/lib/telnet.c b/lib/telnet.c index 79e1627c834a..07b2513b6edd 100644 --- a/lib/telnet.c +++ b/lib/telnet.c @@ -225,16 +225,16 @@ static CURLcode init_telnet(struct Curl_easy *data) static void send_negotiation(struct Curl_easy *data, int cmd, int option) { unsigned char buf[3]; - ssize_t bytes_written; - struct connectdata *conn = data->conn; + size_t nwritten; + CURLcode result; buf[0] = CURL_IAC; buf[1] = (unsigned char)cmd; buf[2] = (unsigned char)option; - bytes_written = swrite(conn->sock[FIRSTSOCKET], buf, 3); - if(bytes_written < 0) - failf(data, "Sending data failed (%d)", SOCKERRNO); + result = Curl_xfer_send(data, buf, 3, FALSE, &nwritten); + if(result) + failf(data, "Sending data failed: %s", curl_easy_strerror(result)); printoption(data, "SENT", cmd, option); } @@ -671,10 +671,10 @@ static CURLcode send_telnet_data(struct Curl_easy *data, static void sendsuboption(struct Curl_easy *data, struct TELNET *tn, int option) { - ssize_t bytes_written; + size_t nwritten; unsigned short x, y; const unsigned char *uc1, *uc2; - struct connectdata *conn = data->conn; + CURLcode result; switch(option) { case CURL_TELOPT_NAWS: @@ -703,16 +703,16 @@ static void sendsuboption(struct Curl_easy *data, CURL_SB_LEN(tn) - 2); /* we send the header of the suboption... */ - bytes_written = swrite(conn->sock[FIRSTSOCKET], tn->subbuffer, 3); - if(bytes_written < 0) - failf(data, "Sending data failed (%d)", SOCKERRNO); + result = Curl_xfer_send(data, tn->subbuffer, 3, FALSE, &nwritten); + if(result) + failf(data, "Sending data failed: %s", curl_easy_strerror(result)); /* ... then the window size with the send_telnet_data() function to deal with 0xFF cases ... */ send_telnet_data(data, tn, (const char *)tn->subbuffer + 3, 4); /* ... and the footer */ - bytes_written = swrite(conn->sock[FIRSTSOCKET], tn->subbuffer + 7, 2); - if(bytes_written < 0) - failf(data, "Sending data failed (%d)", SOCKERRNO); + result = Curl_xfer_send(data, tn->subbuffer + 7, 2, FALSE, &nwritten); + if(result) + failf(data, "Sending data failed: %s", curl_easy_strerror(result)); break; } } @@ -972,9 +972,9 @@ static CURLcode suboption(struct Curl_easy *data, struct TELNET *tn) { struct curl_slist *v; unsigned char temp[2048]; - ssize_t bytes_written; + size_t nwritten; size_t len; - struct connectdata *conn = data->conn; + CURLcode result = CURLE_OK; if(!CURL_SB_LEN(tn)) /* ignore empty suboption */ return CURLE_OK; @@ -993,11 +993,10 @@ static CURLcode suboption(struct Curl_easy *data, struct TELNET *tn) CURL_IAC, CURL_SB, CURL_TELOPT_TTYPE, CURL_TELQUAL_IS, tn->subopt_ttype, CURL_IAC, CURL_SE); - bytes_written = swrite(conn->sock[FIRSTSOCKET], temp, len); - - if(bytes_written < 0) { - failf(data, "Sending data failed (%d)", SOCKERRNO); - return CURLE_SEND_ERROR; + result = Curl_xfer_send(data, temp, len, FALSE, &nwritten); + if(result) { + failf(data, "Sending data failed: %s", curl_easy_strerror(result)); + return result; } printsub(data, '>', &temp[2], len-2); break; @@ -1012,10 +1011,10 @@ static CURLcode suboption(struct Curl_easy *data, struct TELNET *tn) CURL_IAC, CURL_SB, CURL_TELOPT_XDISPLOC, CURL_TELQUAL_IS, tn->subopt_xdisploc, CURL_IAC, CURL_SE); - bytes_written = swrite(conn->sock[FIRSTSOCKET], temp, len); - if(bytes_written < 0) { - failf(data, "Sending data failed (%d)", SOCKERRNO); - return CURLE_SEND_ERROR; + result = Curl_xfer_send(data, temp, len, FALSE, &nwritten); + if(result) { + failf(data, "Sending data failed: %s", curl_easy_strerror(result)); + return result; } printsub(data, '>', &temp[2], len - 2); break; @@ -1044,9 +1043,11 @@ static CURLcode suboption(struct Curl_easy *data, struct TELNET *tn) curl_msnprintf((char *)&temp[len], sizeof(temp) - len, "%c%c", CURL_IAC, CURL_SE); len += 2; - bytes_written = swrite(conn->sock[FIRSTSOCKET], temp, len); - if(bytes_written < 0) - failf(data, "Sending data failed (%d)", SOCKERRNO); + result = Curl_xfer_send(data, temp, len, FALSE, &nwritten); + if(result) { + failf(data, "Sending data failed: %s", curl_easy_strerror(result)); + return result; + } printsub(data, '>', &temp[2], len - 2); break; } From ef362b78ac4349c3160c498fffccb5b69e65b15d Mon Sep 17 00:00:00 2001 From: Stefan Eissing Date: Mon, 14 Sep 2026 11:43:53 +0200 Subject: [PATCH 131/481] multi-event: fix timeout of pending transfers The multi's "run dirty" loop ignored transfers that are not in processing state, e.g. pending transfers. That led to timeouts not being acted upon for those transfers. Fixed by moving a "dirty" pending transfer to the connect state and processing it. Added test_02_37 for verification. Closes #22942 --- docs/libcurl/libcurl-env-dbg.md | 4 ++++ lib/multi.c | 27 ++++++++++++++++++++++----- tests/http/test_02_download.py | 27 +++++++++++++++++++++++++++ 3 files changed, 53 insertions(+), 5 deletions(-) diff --git a/docs/libcurl/libcurl-env-dbg.md b/docs/libcurl/libcurl-env-dbg.md index 7845f5c0f5d5..08d6eda3fdbf 100644 --- a/docs/libcurl/libcurl-env-dbg.md +++ b/docs/libcurl/libcurl-env-dbg.md @@ -224,3 +224,7 @@ This allows testing of connection reuse in mixed `STARTTLS` needs. ## `CURL_DBG_SUPPRESS_CONNECT_HDS` Existence of this variable suppresses the collection of CONNECT headers. + +## `CURL_DBG_MAX_TOTAL_CONNECTIONS` + +Set the `max_total_connections` for a `multi` handle. diff --git a/lib/multi.c b/lib/multi.c index 580b9c7c8f10..2c2548f0321b 100644 --- a/lib/multi.c +++ b/lib/multi.c @@ -47,6 +47,7 @@ #include "http2.h" #include "socketpair.h" #include "bufref.h" +#include "curlx/strparse.h" /* initial multi->xfers table size for a full multi */ #define CURL_XFER_TABLE_SIZE 128 @@ -283,8 +284,18 @@ struct Curl_multi *Curl_multi_handle(uint32_t xfer_table_size, goto error; #ifdef DEBUGBUILD - if(getenv("CURL_DEBUG")) - multi->admin->set.verbose = TRUE; + { + const char *p; + if(getenv("CURL_DEBUG")) + multi->admin->set.verbose = TRUE; + p = getenv("CURL_DBG_MAX_TOTAL_CONNECTIONS"); + if(p) { + curl_off_t l; + if(!curlx_str_number(&p, &l, UINT32_MAX)) { + multi->max_total_connections = (uint32_t)l; + } + } + } #endif Curl_uint32_tbl_add(&multi->xfers, multi->admin, &multi->admin->mid); Curl_uint32_bset_add(&multi->process, multi->admin->mid); @@ -3206,9 +3217,15 @@ static CURLMcode multi_run_dirty(struct Curl_multi *multi, CURL_TRC_M(data, "multi_run_dirty"); if(!Curl_uint32_bset_contains(&multi->process, mid)) { - /* We are no longer processing this transfer */ - Curl_uint32_bset_remove(&multi->dirty, mid); - continue; + if(Curl_uint32_bset_contains(&multi->pending, mid)) { + /* Something happened on the pending transfer, act on it. */ + move_pending_to_connect(multi, data); + } + else { + /* We are no longer processing this transfer */ + Curl_uint32_bset_remove(&multi->dirty, mid); + continue; + } } (*pnum)++; diff --git a/tests/http/test_02_download.py b/tests/http/test_02_download.py index 473502809b46..493d230fda56 100644 --- a/tests/http/test_02_download.py +++ b/tests/http/test_02_download.py @@ -716,3 +716,30 @@ def test_02_36_looong_urls(self, env: Env, httpd, nghttpx, proto, url_junk): f'got {r.exit_code}\n{r.dump_logs()}' if r.exit_code == 0: r.check_response(http_status=431) + + # download 2 http/1.1 resources, limited to a single connection max + @pytest.mark.skipif(condition=not Env.curl_is_debug(), reason="needs curl debug") + def test_02_37_pending_timeout(self, env: Env, httpd): + # Run 2 http/1.1 transfers in parallel with a connection limit of 1 + # - xfer 0 has a delay response, making xfer 1 pending during its run + # - xfer 1 will timeout while pending, waiting on the connection + proto = 'http/1.1' + run_env = os.environ.copy() + run_env['CURL_DBG_MAX_TOTAL_CONNECTIONS'] = '1' + curl = CurlClient(env=env, run_env=run_env) + url1 = f'https://{env.authority_for(env.domain1, proto)}' \ + '/curltest/tweak/?&delay=3s' + url2 = f'https://{env.authority_for(env.domain1, proto)}/data.json' + r = curl.http_download(urls=[url1, url2], alpn_proto=proto, extra_args=[ + '--http1.1', '--parallel' + ], url_options={ + url1: ['--max-time', '10'], + url2: ['--max-time', '1'] + }) + r.check_exit_code(28) + xfers = {stat['xfer_id']: stat for stat in r.stats} + assert xfers[0]['http_code'] == 200, f'{r.stats[0]}' # succeeded + assert xfers[1]['exitcode'] == 28, f'{r.stats[1]}' # timed out + m = re.search(r'timed out after (\d+) milliseconds', xfers[1]['errormsg']) + assert m, f'{r.stats[1]}' + assert int(m.group(1)) < 3000, f'{r.stats[1]}' From f4caaa2354d45d40dd07c6d09712dbc502adf79a Mon Sep 17 00:00:00 2001 From: Daniel Stenberg Date: Mon, 14 Sep 2026 14:57:25 +0200 Subject: [PATCH 132/481] urlapi: reject more junk from the zone id Extended test 1560 to verify Closes #22949 --- lib/urlapi.c | 32 ++++++++++++++++---------------- tests/libtest/lib1560.c | 3 +++ 2 files changed, 19 insertions(+), 16 deletions(-) diff --git a/lib/urlapi.c b/lib/urlapi.c index 339803321e76..1c4db04e4638 100644 --- a/lib/urlapi.c +++ b/lib/urlapi.c @@ -440,6 +440,20 @@ UNITTEST CURLUcode parse_port(struct Curl_URL *u, struct dynbuf *host, return CURLUE_OK; } +/* characters not allowed in hostnames: + " \r\n\t/:#?!@{}[]\\$\'\"^`*<>=;,+&()%|" */ + +static const bool invalid_host_char[256] = { + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 0x00-0x0F */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 0x10-0x1F */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, /* 0x20-0x2F */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, /* 0x30-0x3F */ + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x40-0x4F */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, /* 0x50-0x5F */ + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x60-0x6F */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1 /* 0x70-0x7F */ +}; + /* This function assumes 'hostname' now starts with [. It trims 'hostname' in * place and it sets u->zoneid if present. * @@ -470,8 +484,8 @@ UNITTEST CURLUcode ipv6_parse(struct Curl_URL *u, char *hostname, /* pass '25' if present and is a URL encoded percent sign */ if(!strncmp(h, "25", 2) && h[2] && (h[2] != ']')) h += 2; - while(*h && (*h != ']') && (i < (MAX_ZONEID_LEN - 1)) && - (*h != ' ')) + while(!invalid_host_char[(unsigned char)*h] && (*h != ']') && + (i < (MAX_ZONEID_LEN - 1))) zoneid[i++] = *h++; if(!i || (']' != *h)) return CURLUE_BAD_IPV6; @@ -502,20 +516,6 @@ UNITTEST CURLUcode ipv6_parse(struct Curl_URL *u, char *hostname, return CURLUE_OK; } -/* characters not allowed in hostnames: - " \r\n\t/:#?!@{}[]\\$\'\"^`*<>=;,+&()%|" */ - -static const bool invalid_host_char[256] = { - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 0x00-0x0F */ - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 0x10-0x1F */ - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, /* 0x20-0x2F */ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, /* 0x30-0x3F */ - 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x40-0x4F */ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, /* 0x50-0x5F */ - 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x60-0x6F */ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1 /* 0x70-0x7F */ -}; - /* the input is a confirmed hostname, never an IPv6 address */ static CURLUcode hostname_check(char *hostname, size_t hlen) { diff --git a/tests/libtest/lib1560.c b/tests/libtest/lib1560.c index ace91e3a99c5..03d99ccbbf3b 100644 --- a/tests/libtest/lib1560.c +++ b/tests/libtest/lib1560.c @@ -893,6 +893,9 @@ static const struct urltestcase get_url_list[] = { {"https://[::%25fa keit]:80/moo", "", 0, 0, CURLUE_BAD_IPV6}, + {"https://[::%25fa%\x7fkeit]:80/moo", + "", + 0, 0, CURLUE_BAD_IPV6}, {"https://[::%25fa keit]:80/moo", "", CURLU_ALLOW_SPACE, 0, CURLUE_BAD_IPV6}, From 91e0ed51a57d65c33a09d912eb4dec23e7efa667 Mon Sep 17 00:00:00 2001 From: Max Dymond Date: Mon, 14 Sep 2026 13:14:03 +0100 Subject: [PATCH 133/481] glob: reduce the number of allocations for URLs that don't need globbing. Closes #22947 --- src/tool_operate.c | 8 +++++--- src/tool_urlglob.c | 17 ++++++++++++++++- src/tool_urlglob.h | 2 ++ tests/data/Makefile.am | 1 + tests/data/test2415 | 36 ++++++++++++++++++++++++++++++++++++ 5 files changed, 60 insertions(+), 4 deletions(-) create mode 100644 tests/data/test2415 diff --git a/src/tool_operate.c b/src/tool_operate.c index 439932f5b6cd..be69c4bde54d 100644 --- a/src/tool_operate.c +++ b/src/tool_operate.c @@ -1242,10 +1242,11 @@ static CURLcode setup_input_file(struct OperationConfig *config, if(!config->globoff && !glob_inuse(&state->inglob)) result = glob_url(&state->inglob, u->infile, &state->upnum, err); if(!result && !state->uploadfile) { - if(glob_inuse(&state->inglob)) + if(glob_inuse(&state->inglob) && + !glob_is_literal(&state->inglob)) result = glob_next_url(&state->uploadfile, &state->inglob); else if(!state->upidx) { - /* copy the allocated string */ + /* take ownership of the allocated string */ state->uploadfile = u->infile; u->infile = NULL; } @@ -1305,7 +1306,8 @@ static CURLcode select_next_url(struct State *state, char **url) { CURLcode result = CURLE_OK; - if(glob_inuse(&state->urlglob)) + if(glob_inuse(&state->urlglob) && + !glob_is_literal(&state->urlglob)) result = glob_next_url(url, &state->urlglob); else if(!state->urlidx) { *url = curlx_strdup(u->url); diff --git a/src/tool_urlglob.c b/src/tool_urlglob.c index 836d6431205d..ddc65b7b8168 100644 --- a/src/tool_urlglob.c +++ b/src/tool_urlglob.c @@ -543,7 +543,13 @@ static CURLcode glob_parse(struct URLGlob *glob, const char *pattern, bool glob_inuse(struct URLGlob *glob) { - return glob->palloc ? TRUE : FALSE; + /* Both parsed patterns and a literal URL make the glob active. */ + return (glob->palloc || glob->literal) ? TRUE : FALSE; +} + +bool glob_is_literal(const struct URLGlob *glob) +{ + return glob->literal; } /* a glob error has been confirmed, this outputs details about it to the set @@ -578,6 +584,12 @@ CURLcode glob_url(struct URLGlob *glob, const char *url, curl_off_t *urlnum, memset(glob, 0, sizeof(struct URLGlob)); curlx_dyn_init(&glob->buf, MAX_CONFIG_LINE_LENGTH); + if(!strpbrk(url, "{}[]")) { + /* no glob syntax: use the URL as-is instead of parsing a copy of it */ + glob->literal = TRUE; + *urlnum = 1; + return CURLE_OK; + } glob->pattern = curlx_malloc(2 * sizeof(struct URLPattern)); if(!glob->pattern) return CURLE_OUT_OF_MEMORY; @@ -598,6 +610,8 @@ void glob_cleanup(struct URLGlob *glob) { size_t i; + glob->literal = FALSE; + glob->beenhere = 0; if(glob->pattern) { for(i = 0; i < glob->pnum; i++) { DEBUGASSERT(glob->pattern[i].type); @@ -622,6 +636,7 @@ CURLcode glob_next_url(char **globbed, struct URLGlob *glob) size_t i; *globbed = NULL; + DEBUGASSERT(!glob->literal); curlx_dyn_reset(&glob->buf); if(!glob->beenhere) diff --git a/src/tool_urlglob.h b/src/tool_urlglob.h index e891258aa46e..020f8d9ffd51 100644 --- a/src/tool_urlglob.h +++ b/src/tool_urlglob.h @@ -65,6 +65,7 @@ struct URLPattern { struct URLGlob { struct dynbuf buf; struct URLPattern *pattern; + bool literal; size_t palloc; /* number of pattern entries allocated */ size_t pnum; /* number of patterns used */ char beenhere; @@ -83,5 +84,6 @@ CURLcode glob_match_url(char **output, const char *filename, SANITIZEcode *sc); void glob_cleanup(struct URLGlob *glob); bool glob_inuse(struct URLGlob *glob); +bool glob_is_literal(const struct URLGlob *glob); #endif /* HEADER_CURL_TOOL_URLGLOB_H */ diff --git a/tests/data/Makefile.am b/tests/data/Makefile.am index 7bc72f365741..ee25967b2f99 100644 --- a/tests/data/Makefile.am +++ b/tests/data/Makefile.am @@ -1964,6 +1964,7 @@ TESTCASES = \ test2412 \ test2413 \ test2414 \ + test2415 \ test2429 \ test2453 \ \ diff --git a/tests/data/test2415 b/tests/data/test2415 new file mode 100644 index 000000000000..4619caeac268 --- /dev/null +++ b/tests/data/test2415 @@ -0,0 +1,36 @@ + + + + +HTTP +HTTP GET +globbing + + +# Server-side + +# Client-side + + +http + + +reference a named glob with a literal URL + + +"%HOSTIP:%HTTPPORT/test" -o "somewhere/#%LTfoo%GT" + + + +# Verify data after the test has been "shot" + + +curl: (43) no glob exists with this name in position 16: +somewhere/#%LTfoo%GT + ^ + + +43 + + + From 32c6bd5303bfafaa80453a7d112d240baf2f8141 Mon Sep 17 00:00:00 2001 From: Joshua Rogers Date: Mon, 14 Sep 2026 10:23:00 +0200 Subject: [PATCH 134/481] hash: vary bucket placement across process runs hash_str() always starts from the same value, so a given key set lands in the same buckets on every run. Mix in a random seed generated at global init. Closes #22916 --- lib/easy.c | 5 +++++ lib/hash.c | 47 ++++++++++++++++++++++++++++++++++++++++--- lib/hash.h | 2 ++ lib/rand.h | 2 ++ tests/unit/unit1603.c | 3 +++ 5 files changed, 56 insertions(+), 3 deletions(-) diff --git a/lib/easy.c b/lib/easy.c index b92f4021567e..721f3a0c7a27 100644 --- a/lib/easy.c +++ b/lib/easy.c @@ -177,6 +177,11 @@ static CURLcode global_init(long flags, bool memoryfuncs) goto fail; } + if(Curl_hash_global_init()) { + DEBUGF(curl_mfprintf(stderr, "Error: Curl_hash_global_init failed\n")); + goto fail; + } + if(!Curl_vquic_init()) { DEBUGF(curl_mfprintf(stderr, "Error: Curl_vquic_init failed\n")); goto fail; diff --git a/lib/hash.c b/lib/hash.c index 050f80771a7f..0b9dc2f5f0c8 100644 --- a/lib/hash.c +++ b/lib/hash.c @@ -26,6 +26,7 @@ #include /* for offsetof() */ #include "hash.h" +#include "rand.h" /* random patterns for API verification */ #ifdef DEBUGBUILD @@ -52,14 +53,46 @@ struct hash_functions { comp_function compare; }; +/* set at global init, read-only afterwards */ +static size_t hash_seed; + +CURLcode Curl_hash_global_init(void) +{ + static bool seeded; + unsigned char buf[sizeof(size_t)]; + size_t seed = 0; + size_t i; + CURLcode result; + + if(seeded) + return CURLE_OK; + + result = Curl_rand_bytes(NULL, +#ifdef DEBUGBUILD + FALSE, +#endif + buf, sizeof(buf)); + if(result) + return result; + + for(i = 0; i < sizeof(buf); i++) + seed = (seed << 8) | buf[i]; + + hash_seed = seed ? seed : 1; + seeded = TRUE; + return CURLE_OK; +} + /* @unittest 1603 */ -UNITTEST size_t hash_str(const void *key, size_t key_length, size_t slots_num); -UNITTEST size_t hash_str(const void *key, size_t key_length, size_t slots_num) +UNITTEST size_t hash_str_seeded(const void *key, size_t key_length, + size_t slots_num, size_t seed); +UNITTEST size_t hash_str_seeded(const void *key, size_t key_length, + size_t slots_num, size_t seed) { const char *key_str = (const char *)key; const char *end = key_str + key_length; - size_t h = 5381; + size_t h = 5381 ^ seed; while(key_str < end) { size_t j = (size_t)*key_str++; @@ -70,6 +103,14 @@ UNITTEST size_t hash_str(const void *key, size_t key_length, size_t slots_num) return (h % slots_num); } +/* @unittest 1603 + */ +UNITTEST size_t hash_str(const void *key, size_t key_length, size_t slots_num); +UNITTEST size_t hash_str(const void *key, size_t key_length, size_t slots_num) +{ + return hash_str_seeded(key, key_length, slots_num, hash_seed); +} + /* Avoid treating the variable-length key[1] as a one-byte object. */ static char *hash_elem_key(struct Curl_hash_element *he) { diff --git a/lib/hash.h b/lib/hash.h index c889131c687b..2becbd919c0f 100644 --- a/lib/hash.h +++ b/lib/hash.h @@ -65,6 +65,8 @@ struct Curl_hash_iterator { #endif }; +CURLcode Curl_hash_global_init(void); + void Curl_hash_init(struct Curl_hash *h, size_t slots, Curl_hash_type type, diff --git a/lib/rand.h b/lib/rand.h index 452c1b67ef90..7d59337ad4e7 100644 --- a/lib/rand.h +++ b/lib/rand.h @@ -23,6 +23,8 @@ * SPDX-License-Identifier: curl * ***************************************************************************/ +struct Curl_easy; + CURLcode Curl_rand_bytes(struct Curl_easy *data, #ifdef DEBUGBUILD bool env_override, diff --git a/tests/unit/unit1603.c b/tests/unit/unit1603.c index c06f47116e99..121654f001ea 100644 --- a/tests/unit/unit1603.c +++ b/tests/unit/unit1603.c @@ -122,6 +122,9 @@ static CURLcode test_unit1603(const char *arg) const char *nodep; int rc; + fail_unless(hash_str_seeded("a", 1, 97, 0x1234) == 95, + "seeded hash is not computed as expected"); + /* Ensure the key hashes are as expected in order to test both hash collisions and a full table. Unfortunately, the hashes can vary between architectures. */ From 35b0490aabf515b817ea2462722306cf118e7747 Mon Sep 17 00:00:00 2001 From: Stefan Eissing Date: Fri, 11 Sep 2026 14:21:55 +0200 Subject: [PATCH 135/481] peer: speed up "same destination" check The dominant use in `Curl_peer_same_destination()` are checks on the very same hostname. Speed up the case-insensitive matching by performing a `strcmp()` first that is platform optimized. Closes #22914 --- lib/peer.c | 9 ++++++--- lib/url.c | 10 ++++++---- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/lib/peer.c b/lib/peer.c index 4a7a12d8cf06..577cedbb334b 100644 --- a/lib/peer.c +++ b/lib/peer.c @@ -333,9 +333,12 @@ static bool peer_same_hostname(struct Curl_peer *p1, struct Curl_peer *p2) return (p1->unix_socket == p2->unix_socket) && (p1->abstract_uds == p2->abstract_uds) && (p1->ipv6 == p2->ipv6) && - (p1->unix_socket ? - !strcmp(p1->hostname, p2->hostname) : - curl_strequal(p1->hostname, p2->hostname)); + /* unix_socket paths are case-sensitive, hostnames are not. + * Do strcmp() first on both, since we do a lot of matches + * on the same names and platform strcmp is way faster. */ + (!strcmp(p1->hostname, p2->hostname) || + (!p1->unix_socket && + curl_strequal(p1->hostname, p2->hostname))); } bool Curl_peer_same_destination(struct Curl_peer *p1, struct Curl_peer *p2) diff --git a/lib/url.c b/lib/url.c index ab8c79fcdd53..47345eb5f85b 100644 --- a/lib/url.c +++ b/lib/url.c @@ -690,14 +690,16 @@ static bool url_match_multiplex_limits(struct connectdata *conn, /* If multiplexed, make sure we do not go over concurrency limit */ if(conn->attached_xfers >= Curl_multi_max_concurrent_streams(m->data->multi)) { - infof(m->data, "client side MAX_CONCURRENT_STREAMS reached" - ", skip (%u)", conn->attached_xfers); + infof(m->data, "connection #%" FMT_OFF_T " reached " + "MAX_CONCURRENT_STREAMS(%u), skip", + conn->connection_id, conn->attached_xfers); return FALSE; } if(conn->attached_xfers >= Curl_conn_get_max_concurrent(m->data, conn, FIRSTSOCKET)) { - infof(m->data, "MAX_CONCURRENT_STREAMS reached, skip (%u)", - conn->attached_xfers); + infof(m->data, "connection #%" FMT_OFF_T "reached " + "MAX_CONCURRENT_STREAMS(%u), skip", + conn->connection_id, conn->attached_xfers); return FALSE; } /* When not multiplexed, we have a match here! */ From cdbe4eb55609e2096e296faf86552f27514d6a01 Mon Sep 17 00:00:00 2001 From: Stephen Psaradellis Date: Mon, 14 Sep 2026 17:34:25 -0500 Subject: [PATCH 136/481] rtsp: do not read a response body when CURLOPT_NOBODY is set Follow-up to 458704d484. That change clears req.no_body for an RTSP response that announces a body with Content-Length, but req.no_body also carries the application's CURLOPT_NOBODY, so such a body was then read even when the application asked for no body. Only override the method-derived value. Add test1688: test657 with CURLOPT_NOBODY set, expecting headers only. Reported-by: Daniel Stenberg Ref: #22935 Closes #22954 --- lib/http.c | 5 ++-- tests/data/Makefile.am | 1 + tests/data/test1688 | 57 ++++++++++++++++++++++++++++++++++++++++++ tests/libtest/lib567.c | 2 ++ 4 files changed, 63 insertions(+), 2 deletions(-) create mode 100644 tests/data/test1688 diff --git a/lib/http.c b/lib/http.c index 7f76a9279f91..3894f53c0fbc 100644 --- a/lib/http.c +++ b/lib/http.c @@ -4213,9 +4213,10 @@ static CURLcode http_on_response(struct Curl_easy *data, #ifndef CURL_DISABLE_RTSP /* An RTSP response to any method may carry a body, which is announced with Content-Length (RFC 2326 section 12.14). Read it instead of - treating it as excess data. */ + treating it as excess data, unless the application asked for no body + with CURLOPT_NOBODY. */ if((conn->scheme->protocol & CURLPROTO_RTSP) && data->req.no_body && - (k->size > 0)) + !data->set.opt_no_body && (k->size > 0)) data->req.no_body = FALSE; #endif diff --git a/tests/data/Makefile.am b/tests/data/Makefile.am index ee25967b2f99..be7a4aa32fbf 100644 --- a/tests/data/Makefile.am +++ b/tests/data/Makefile.am @@ -1706,6 +1706,7 @@ TESTCASES = \ test1685 \ test1686 \ test1687 \ + test1688 \ \ test1700 \ test1702 \ diff --git a/tests/data/test1688 b/tests/data/test1688 new file mode 100644 index 000000000000..357591baa268 --- /dev/null +++ b/tests/data/test1688 @@ -0,0 +1,57 @@ + + + + +RTSP +OPTIONS + + + +# Server-side + + +RTSP/1.0 200 OK +Server: RTSPD/libcurl-test +CSeq: 1 +Public: DESCRIBE, OPTIONS, SETUP, TEARDOWN, PLAY, PAUSE +Content-Length: 18 + +No-More: sessions + + + +# Client-side + + +rtsp + + +lib567 + + +RTSP OPTIONS response with a body, CURLOPT_NOBODY set + + +rtsp://%HOSTIP:%RTSPPORT/%TESTNUMBER + + + +# Verify data after the test has been "shot" + + +OPTIONS rtsp://%HOSTIP:%RTSPPORT/%TESTNUMBER RTSP/1.0 +CSeq: 1 +User-Agent: test567 +Test-Number: 567 + + + +RTSP/1.0 200 OK +Server: RTSPD/libcurl-test +CSeq: 1 +Public: DESCRIBE, OPTIONS, SETUP, TEARDOWN, PLAY, PAUSE +Content-Length: 18 + + + + diff --git a/tests/libtest/lib567.c b/tests/libtest/lib567.c index dc17f53b0ba4..1421dfb157e4 100644 --- a/tests/libtest/lib567.c +++ b/tests/libtest/lib567.c @@ -53,6 +53,8 @@ static CURLcode test_lib567(const char *URL) easy_setopt(curl, CURLOPT_RTSP_STREAM_URI, URL); easy_setopt(curl, CURLOPT_RTSP_REQUEST, CURL_RTSPREQ_OPTIONS); easy_setopt(curl, CURLOPT_USERAGENT, "test567"); + if(testnum == 1688) + easy_setopt(curl, CURLOPT_NOBODY, 1L); custom_headers = curl_slist_append(custom_headers, "Test-Number: 567"); easy_setopt(curl, CURLOPT_RTSPHEADER, custom_headers); From 445aa87e13f91c94bc742f54343a2eac924e1765 Mon Sep 17 00:00:00 2001 From: Daniel Stenberg Date: Wed, 16 Sep 2026 11:09:18 +0200 Subject: [PATCH 137/481] cookie: accept slightly longer lines in cookie jars Closes #22964 --- lib/cookie.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/cookie.c b/lib/cookie.c index 5077b598497d..3c0ebee91ce0 100644 --- a/lib/cookie.c +++ b/lib/cookie.c @@ -1181,7 +1181,9 @@ static CURLcode cookie_load(struct Curl_easy *data, const char *file, if(fp) { struct dynbuf buf; bool eof = FALSE; - curlx_dyn_init(&buf, MAX_COOKIE_LINE); + /* accept a little longer lines so that a full cookie fits and does not + abort the file read */ + curlx_dyn_init(&buf, MAX_COOKIE_LINE + 48); do { result = Curl_get_line(&buf, fp, &eof); if(!result) { From 9315d9c4e4b77d8e6f16206780d431a25735c3bf Mon Sep 17 00:00:00 2001 From: Max Dymond Date: Wed, 16 Sep 2026 14:34:33 +0100 Subject: [PATCH 138/481] http2: initialize URL handle for server pushes Retain the parsed CURLU on accepted pushed transfers so retries and relative redirects have valid URL state. Add regression coverage for relative redirects on pushed streams. Closes #22970 --- lib/http2.c | 7 +++++-- tests/http/test_09_push.py | 3 +++ tests/libtest/cli_h2_serverpush.c | 7 +++++++ 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/lib/http2.c b/lib/http2.c index 792da468f877..f6b8a77f0965 100644 --- a/lib/http2.c +++ b/lib/http2.c @@ -762,10 +762,13 @@ static int set_transfer_url(struct Curl_easy *newhandle, if(uc) rc = 4; fail: - curl_url_cleanup(u); - if(rc) + if(rc) { + curl_url_cleanup(u); return rc; + } + DEBUGASSERT(!newhandle->state.uh); + newhandle->state.uh = u; Curl_bufref_set(&newhandle->state.url, url, 0, curl_free); return 0; } diff --git a/tests/http/test_09_push.py b/tests/http/test_09_push.py index 7fb37b679df3..7472b3d8e39c 100644 --- a/tests/http/test_09_push.py +++ b/tests/http/test_09_push.py @@ -49,6 +49,7 @@ def httpd_configure(self, env, httpd): ' H2PushResource /push/data2', '', '', + ' Header set Location "/push/data3"', ' H2PushResource /push/data1', ' H2PushResource /push/data3', '', @@ -82,3 +83,5 @@ def test_09_02_h2_push(self, env: Env, httpd, configures_httpd): r.check_exit_code(0) assert os.path.exists(client.download_file(0)) assert os.path.exists(os.path.join(client.run_dir, 'push0')), r.dump_logs() + redirect_url = f'https://localhost:{env.https_port}/push/data3' + assert f'**** push redirect URL: {redirect_url}' in r.stderr diff --git a/tests/libtest/cli_h2_serverpush.c b/tests/libtest/cli_h2_serverpush.c index 8a16b01eba53..4c7832372e7e 100644 --- a/tests/libtest/cli_h2_serverpush.c +++ b/tests/libtest/cli_h2_serverpush.c @@ -169,6 +169,13 @@ static CURLcode test_cli_h2_serverpush(const char *URL) m = curl_multi_info_read(multi, &msgq); if(m && (m->msg == CURLMSG_DONE)) { CURL *easy = m->easy_handle; + if(easy != curl) { + char *redirect_url = NULL; + if(!curl_easy_getinfo(easy, CURLINFO_REDIRECT_URL, + &redirect_url) && redirect_url) + curl_mfprintf(stderr, "**** push redirect URL: %s\n", + redirect_url); + } transfers--; curl_multi_remove_handle(multi, easy); curl_easy_cleanup(easy); From a2fa1be85bf7d290370a15783bc1f182e2984bfb Mon Sep 17 00:00:00 2001 From: Joshua Rogers Date: Fri, 11 Sep 2026 20:18:36 +0200 Subject: [PATCH 139/481] lib: reject internal handles in the multi/easy public API - Change curl_multi_add_handle(), curl_multi_remove_handle() and Curl_eapi_enter() to refuse easy handles with state.internal set. Prior to this change, a notify callback that removed and cleaned up the handle it gets for CURLMNOTIFY_INFO_READ freed multi->admin, leading to a use-after-free and a double-free in curl_multi_cleanup(). Co-authored-by: Stefan Eissing Closes https://github.com/curl/curl/pull/22917 Closes https://github.com/curl/curl/pull/22966 --- docs/libcurl/curl_multi_remove_handle.md | 4 + docs/libcurl/opts/CURLMOPT_NOTIFYFUNCTION.md | 5 + lib/api.c | 2 +- lib/multi.c | 4 +- tests/data/Makefile.am | 1 + tests/data/test3107 | 47 +++++++++ tests/libtest/Makefile.inc | 1 + tests/libtest/lib3107.c | 105 +++++++++++++++++++ 8 files changed, 166 insertions(+), 3 deletions(-) create mode 100644 tests/data/test3107 create mode 100644 tests/libtest/lib3107.c diff --git a/docs/libcurl/curl_multi_remove_handle.md b/docs/libcurl/curl_multi_remove_handle.md index fed6439c4489..a8ecdfec64c6 100644 --- a/docs/libcurl/curl_multi_remove_handle.md +++ b/docs/libcurl/curl_multi_remove_handle.md @@ -40,6 +40,10 @@ handles and transfers remain unaffected. It is fine to remove a handle at any time during a transfer, but not from within any libcurl callback function. +An internal handle, such as one passed to a CURLMOPT_NOTIFYFUNCTION(3) +callback, cannot be removed and makes this function return +CURLM_BAD_EASY_HANDLE. + Removing an easy handle from the multi handle before the corresponding transfer is complete might cause libcurl to close the connection - if the state of it and the internal protocol handler deem it necessary. Otherwise diff --git a/docs/libcurl/opts/CURLMOPT_NOTIFYFUNCTION.md b/docs/libcurl/opts/CURLMOPT_NOTIFYFUNCTION.md index 0e10aa918787..cd57682c24af 100644 --- a/docs/libcurl/opts/CURLMOPT_NOTIFYFUNCTION.md +++ b/docs/libcurl/opts/CURLMOPT_NOTIFYFUNCTION.md @@ -51,6 +51,11 @@ and curl_multi_cleanup(3) it may call all other methods on the multi and easy handles. This includes adding and removing easy handles to/from the multi handle. +Internal handles are owned by libcurl and the public API rejects them: the +easy interface functions fail with CURLE_BAD_FUNCTION_ARGUMENT, and +curl_multi_add_handle(3) and curl_multi_remove_handle(3) return +CURLM_BAD_EASY_HANDLE. + This callback may get invoked at any time when interacting with libcurl. This may even happen after all transfers are done and *may also* happen *during* a call to curl_multi_cleanup(3) when cached connections diff --git a/lib/api.c b/lib/api.c index 1636d006d275..acf372cec802 100644 --- a/lib/api.c +++ b/lib/api.c @@ -196,7 +196,7 @@ bool Curl_eapi_enter(struct Curl_eapi_guard *guard, guard->depth = 0; /* Verify that we got an easy handle we can work with. */ - if(!GOOD_EASY_HANDLE(data)) { + if(!GOOD_EASY_HANDLE(data) || data->state.internal) { result = CURLE_BAD_FUNCTION_ARGUMENT; goto out; } diff --git a/lib/multi.c b/lib/multi.c index 2c2548f0321b..b8520a0ed58e 100644 --- a/lib/multi.c +++ b/lib/multi.c @@ -581,7 +581,7 @@ CURLMcode curl_multi_add_handle(CURLM *m, CURL *curl) if(CURL_MAPI_ENTER(&guard, m, multi_add_handle, &mresult)) { struct Curl_easy *data = curl; /* Verify that we got a somewhat good easy handle too */ - if(!GOOD_EASY_HANDLE(data)) + if(!GOOD_EASY_HANDLE(data) || data->state.internal) mresult = CURLM_BAD_EASY_HANDLE; else mresult = Curl_multi_add_handle(m, data); @@ -901,7 +901,7 @@ CURLMcode curl_multi_remove_handle(CURLM *m, CURL *curl) if(CURL_MAPI_ENTER(&guard, m, multi_remove_handle, &mresult)) { struct Curl_easy *data = curl; - if(!GOOD_EASY_HANDLE(data)) + if(!GOOD_EASY_HANDLE(data) || data->state.internal) mresult = CURLM_BAD_EASY_HANDLE; else mresult = Curl_multi_remove_handle(m, data); diff --git a/tests/data/Makefile.am b/tests/data/Makefile.am index be7a4aa32fbf..2aa0cac66988 100644 --- a/tests/data/Makefile.am +++ b/tests/data/Makefile.am @@ -2057,6 +2057,7 @@ TESTCASES = \ test3104 \ test3105 \ test3106 \ + test3107 \ test3109 \ test3200 \ test3201 \ diff --git a/tests/data/test3107 b/tests/data/test3107 new file mode 100644 index 000000000000..3ff68b5d5a78 --- /dev/null +++ b/tests/data/test3107 @@ -0,0 +1,47 @@ + + + + +HTTP +HTTP GET +multi +curl_multi_remove_handle + + + +# Server-side + + +HTTP/1.1 200 OK +Content-Length: 6 + +hello + + + +# Client-side + + +http + + +lib%TESTNUMBER + + +reject internal easy handle passed to a notify callback + + +http://%HOSTIP:%HTTPPORT/%TESTNUMBER + + + +# Verify data after the test has been "shot" + + +GET /%TESTNUMBER HTTP/1.1 +Host: %HOSTIP:%HTTPPORT +Accept: */* + + + + diff --git a/tests/libtest/Makefile.inc b/tests/libtest/Makefile.inc index d7651a34115d..6ee49d0717cf 100644 --- a/tests/libtest/Makefile.inc +++ b/tests/libtest/Makefile.inc @@ -329,6 +329,7 @@ TESTS_C = \ lib3103.c \ lib3104.c \ lib3105.c \ + lib3107.c \ lib3207.c \ lib3208.c \ lib3231.c \ diff --git a/tests/libtest/lib3107.c b/tests/libtest/lib3107.c new file mode 100644 index 000000000000..6ae3f3e11759 --- /dev/null +++ b/tests/libtest/lib3107.c @@ -0,0 +1,105 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ +#include "first.h" + +static int t3107_notified; +static CURLMcode t3107_mres; +static CURLcode t3107_eres; + +static void t3107_notify_cb(CURLM *multi, unsigned int notification, + CURL *easy, void *user_data) +{ + (void)user_data; + if(notification != CURLMNOTIFY_INFO_READ) + return; + t3107_notified++; + t3107_mres = curl_multi_remove_handle(multi, easy); + t3107_eres = curl_easy_setopt(easy, CURLOPT_VERBOSE, 1L); + curl_easy_cleanup(easy); +} + +static CURLcode test_lib3107(const char *URL) +{ + CURL *curl = NULL; + CURLM *multi = NULL; + CURLcode result = CURLE_OK; + CURLMsg *msg; + int running = 1; + int queued; + + global_init(CURL_GLOBAL_ALL); + + multi_init(multi); + multi_setopt(multi, CURLMOPT_NOTIFYFUNCTION, t3107_notify_cb); + + if(curl_multi_notify_enable(multi, CURLMNOTIFY_INFO_READ) != CURLM_OK) { + curl_mfprintf(stderr, "curl_multi_notify_enable() failed\n"); + result = TEST_ERR_MAJOR_BAD; + goto test_cleanup; + } + + easy_init(curl); + easy_setopt(curl, CURLOPT_URL, URL); + + multi_add_handle(multi, curl); + + while(running) { + CURLMcode mres = curl_multi_perform(multi, &running); + if(!mres && running) + mres = curl_multi_poll(multi, NULL, 0, 1000, NULL); + if(mres) { + curl_mfprintf(stderr, "curl_multi failed, with code %d (%s)\n", + (int)mres, curl_multi_strerror(mres)); + result = TEST_ERR_MULTI; + goto test_cleanup; + } + } + + do { + msg = curl_multi_info_read(multi, &queued); + } while(msg); + + if(!t3107_notified) { + curl_mfprintf(stderr, "no CURLMNOTIFY_INFO_READ notification\n"); + result = TEST_ERR_MAJOR_BAD; + } + else if(t3107_mres != CURLM_BAD_EASY_HANDLE) { + curl_mfprintf(stderr, "curl_multi_remove_handle() returned %d\n", + (int)t3107_mres); + result = TEST_ERR_MAJOR_BAD; + } + else if(t3107_eres != CURLE_BAD_FUNCTION_ARGUMENT) { + curl_mfprintf(stderr, "curl_easy_setopt() returned %d\n", + (int)t3107_eres); + result = TEST_ERR_MAJOR_BAD; + } + +test_cleanup: + curl_multi_remove_handle(multi, curl); + curl_multi_cleanup(multi); + curl_easy_cleanup(curl); + curl_global_cleanup(); + + return result; +} From 3a397f7a05a46521b2772670b92ffda53fe05598 Mon Sep 17 00:00:00 2001 From: Stefan Eissing Date: Mon, 14 Sep 2026 12:45:56 +0200 Subject: [PATCH 140/481] http2: update a refused stream's error after GOAWAY When a server announced in a GOAWAY message that it will not process a stream, make the error code from the GOAWAY the stream's error code. Prior to this change GOAWAY HTTP_1_1_REQUIRED did not affect the refused stream's error and libcurl wrongly retried it with http/2. Reported-by: Adam Shirt Fixes https://github.com/curl/curl/issues/22923 Closes https://github.com/curl/curl/pull/22946 --- lib/http2.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/lib/http2.c b/lib/http2.c index f6b8a77f0965..c3604f39d134 100644 --- a/lib/http2.c +++ b/lib/http2.c @@ -1335,8 +1335,16 @@ static int on_stream_close(nghttp2_session *session, int32_t stream_id, stream->closed = TRUE; stream->error = error_code; - if(stream->error) + if(stream->error) { stream->reset = TRUE; + if((stream->error == NGHTTP2_REFUSED_STREAM) && + ctx->rcvd_goaway && ctx->goaway_error) + /* REFUSED_STREAM is used by nghttp2 on a GOAWAY from the + * server where it indicated it will no longer process this stream. + * If the GOAWAY carried an error code, take that as the error for + * the stream. */ + stream->error = ctx->goaway_error; + } if(stream->error) CURL_TRC_CF(data_s, cf, "[%d] RESET: %s (err %u)", From 63dd390fab905a239a97e2700532929343ebc4e9 Mon Sep 17 00:00:00 2001 From: Daniel Stenberg Date: Wed, 16 Sep 2026 23:46:44 +0200 Subject: [PATCH 141/481] tftp: verify direction in a few places Closes #22974 --- lib/tftp.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/lib/tftp.c b/lib/tftp.c index 156aeb9e5595..a1413b3788e4 100644 --- a/lib/tftp.c +++ b/lib/tftp.c @@ -366,6 +366,9 @@ static CURLcode tftp_tx(struct tftp_conn *state, tftp_event_t event) char *bufptr; bool eos; + if(!data->state.upload) + return CURLE_TFTP_ILLEGAL; + switch(event) { case TFTP_EVENT_ACK: @@ -773,10 +776,14 @@ static CURLcode tftp_send_first(struct tftp_conn *state, break; case TFTP_EVENT_ACK: /* Connected for transmit */ + if(!data->state.upload) + return CURLE_TFTP_ILLEGAL; result = tftp_connect_for_tx(state, event); break; case TFTP_EVENT_DATA: /* Connected for receive */ + if(data->state.upload) + return CURLE_TFTP_ILLEGAL; result = tftp_connect_for_rx(state, event); break; @@ -1077,6 +1084,8 @@ static CURLcode tftp_receive_packet(struct Curl_easy *data, switch(state->event) { case TFTP_EVENT_DATA: + if(data->state.upload) + return CURLE_TFTP_ILLEGAL; /* Do not pass to the client empty or retransmitted packets */ if(state->rbytes > 4 && (NEXT_BLOCKNUM(state->block) == getrpacketblock(&state->rpacket))) { From ba8f8a7f27a5c5e19e4ef212b90b35a93559163e Mon Sep 17 00:00:00 2001 From: Daniel Stenberg Date: Wed, 16 Sep 2026 15:17:37 +0200 Subject: [PATCH 142/481] ftplistparser: no more overwriting set.write_func The entries in the 'set' struct should remain as set by the application. Closes #22969 --- lib/cw-out.c | 10 ++++++++-- lib/ftp-int.h | 5 ----- lib/ftp.c | 13 ------------- lib/ftplistparser.c | 10 ++++++++++ lib/ftplistparser.h | 5 +++++ 5 files changed, 23 insertions(+), 20 deletions(-) diff --git a/lib/cw-out.c b/lib/cw-out.c index e1f8abe18a94..e451d2399374 100644 --- a/lib/cw-out.c +++ b/lib/cw-out.c @@ -142,8 +142,14 @@ static void cw_get_writefunc(struct Curl_easy *data, cw_out_type otype, switch(otype) { case CW_OUT_BODY: case CW_OUT_BODY_0LEN: - *pwcb = data->set.fwrite_func; - *pwcb_data = data->set.out; + if(Curl_ftp_listmode(data)) { + *pwcb = Curl_ftp_parselist; + *pwcb_data = data; + } + else { + *pwcb = data->set.fwrite_func; + *pwcb_data = data->set.out; + } *pmax_write = CURL_MAX_WRITE_SIZE; /* if we ever want buffering of BODY output, we can set `min_write` * the preferred size. The default should always be to pass data diff --git a/lib/ftp-int.h b/lib/ftp-int.h index 8d7e14127488..0bf6b9a24577 100644 --- a/lib/ftp-int.h +++ b/lib/ftp-int.h @@ -79,11 +79,6 @@ struct ftp_parselist_data; /* defined later in ftplistparser.c */ struct ftp_wc { struct ftp_parselist_data *parser; - - struct { - curl_write_callback write_function; - FILE *file_descriptor; - } backup; }; /* This FTP struct is used in the Curl_easy. All FTP data that is diff --git a/lib/ftp.c b/lib/ftp.c index 94b29c9a247d..ae79592b23e9 100644 --- a/lib/ftp.c +++ b/lib/ftp.c @@ -4016,15 +4016,6 @@ static CURLcode init_wc_data(struct Curl_easy *data, goto fail; } - /* backup old write_function */ - ftpwc->backup.write_function = data->set.fwrite_func; - /* parsing write function */ - data->set.fwrite_func = Curl_ftp_parselist; - /* backup old file descriptor */ - ftpwc->backup.file_descriptor = data->set.out; - /* let the writefunc callback know the transfer */ - data->set.out = data; - infof(data, "Wildcard - Parsing started"); return CURLE_OK; @@ -4060,10 +4051,6 @@ static CURLcode wc_statemach(struct Curl_easy *data, /* In this state is LIST response successfully parsed, so lets restore previous WRITEFUNCTION callback and WRITEDATA pointer */ struct ftp_wc *ftpwc = wildcard->ftpwc; - data->set.fwrite_func = ftpwc->backup.write_function; - data->set.out = ftpwc->backup.file_descriptor; - ftpwc->backup.write_function = ZERO_NULL; - ftpwc->backup.file_descriptor = NULL; wildcard->state = CURLWC_DOWNLOADING; if(Curl_ftp_parselist_geterror(ftpwc->parser)) { diff --git a/lib/ftplistparser.c b/lib/ftplistparser.c index 47e3d75d940a..2ee5fe3faf9d 100644 --- a/lib/ftplistparser.c +++ b/lib/ftplistparser.c @@ -1012,6 +1012,16 @@ static CURLcode parse_winnt(struct Curl_easy *data, return CURLE_OK; } +/* returns TRUE if in wildcard listing mode */ +bool Curl_ftp_listmode(struct Curl_easy *data) +{ + if(data->state.wildcardmatch) { + struct WildcardData * const wildcard = data->wildcard; + return wildcard && (wildcard->state <= CURLWC_MATCHING); + } + return FALSE; +} + size_t Curl_ftp_parselist(char *buffer, size_t size, size_t nmemb, void *connptr) { diff --git a/lib/ftplistparser.h b/lib/ftplistparser.h index 5d7aa492ba4e..942e1028945d 100644 --- a/lib/ftplistparser.h +++ b/lib/ftplistparser.h @@ -27,6 +27,9 @@ #ifndef CURL_DISABLE_FTP +/* returns TRUE if in wildcard listing mode */ +bool Curl_ftp_listmode(struct Curl_easy *data); + /* WRITEFUNCTION callback for parsing LIST responses */ size_t Curl_ftp_parselist(char *buffer, size_t size, size_t nmemb, void *connptr); @@ -71,6 +74,8 @@ void Curl_wildcard_dtor(struct WildcardData **wcp); struct Curl_easy; #else /* CURL_DISABLE_FTP */ +#define Curl_ftp_listmode(x) FALSE +#define Curl_ftp_parselist ZERO_NULL #define Curl_wildcard_dtor(x) #endif /* !CURL_DISABLE_FTP */ From b31c35238d5488fa00185c319f7059938cb474d5 Mon Sep 17 00:00:00 2001 From: Daniel Stenberg Date: Thu, 17 Sep 2026 08:24:20 +0200 Subject: [PATCH 143/481] tests/perf/dateparser: add Closes #22976 --- tests/perf/Makefile.inc | 1 + tests/perf/README.md | 31 +++-- tests/perf/dateparser.c | 270 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 290 insertions(+), 12 deletions(-) create mode 100644 tests/perf/dateparser.c diff --git a/tests/perf/Makefile.inc b/tests/perf/Makefile.inc index 57ab8066fb52..456fbae2f129 100644 --- a/tests/perf/Makefile.inc +++ b/tests/perf/Makefile.inc @@ -45,6 +45,7 @@ CURLX_C = \ TESTS_C = \ base64dec.c \ base64enc.c \ + dateparser.c \ maprintf.c \ simpleformat.c \ snprintf.c \ diff --git a/tests/perf/README.md b/tests/perf/README.md index 95551f600470..ebec32e4e901 100644 --- a/tests/perf/README.md +++ b/tests/perf/README.md @@ -16,14 +16,6 @@ before? Ideally these tests run without using any servers. -## `urlparser` - -Provide this test with a list of many URLs and it times how fast it can parse -them: `./perf urlparser URLs.txt [loops]` - -A test sample of 100K URLs can be found -[here](https://raw.githubusercontent.com/ada-url/url-various-datasets/refs/heads/main/top100/top100.txt) - ## `base64enc` This test base64 encodes a 256-byte buffer that has every different byte octet @@ -38,12 +30,12 @@ provided number of times: ./perf base64dec [loops] -## `snprintf` +## `dateparser` -This test repeatedly formats a representative string and measures the time per -call: +Runs through 180+ date strings and parses them (converts them to epoch time) +`loops` number of times: - ./perf snprintf [loops] + ./perf dateparser [loops] ## `maprintf` @@ -63,6 +55,13 @@ this measures the common one: ./perf simpleformat [loops] +## `snprintf` + +This test repeatedly formats a representative string and measures the time per +call: + + ./perf snprintf [loops] + ## `urlencode` This test URL (percent) encodes a 256-byte buffer that has every different @@ -76,3 +75,11 @@ This test URL (percent) decodes a 600+ byte buffer. Repeatedly in a loop the provided number of times: ./perf urldecode [loops] + +## `urlparser` + +Provide this test with a list of many URLs and it times how fast it can parse +them: `./perf urlparser URLs.txt [loops]` + +A test sample of 100K URLs can be found +[here](https://raw.githubusercontent.com/ada-url/url-various-datasets/refs/heads/main/top100/top100.txt) diff --git a/tests/perf/dateparser.c b/tests/perf/dateparser.c new file mode 100644 index 000000000000..5d8b3c6c2ead --- /dev/null +++ b/tests/perf/dateparser.c @@ -0,0 +1,270 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ +#include "first.h" + +static const char *dates[] = { + "Sun, 06 Nov 1994 08:49:37 GMT", + "Sunday, 06-Nov-94 08:49:37 GMT", + "Sun Nov 6 08:49:37 1994", + "Sun Nov 6 8:49:37 1994", + "Sun Nov 6 8:9:37 1994", + "Sun Nov 6 008:09:37 1994", + "Nov Sun 6 8:9:7 1994", + "06 Nov 1994 08:49:37 GMT", + "06-Nov-94 08:49:37 GMT", + "Nov 6 08:49:37 1994", + "06 Nov 1994 08:49:37", + "06-Nov-94 08:49:37", + "1994 Nov 6 08:49:37", + "GMT 08:49:37 06-Nov-94 Sunday", + "94 6 Nov 08:49:37", + "1994 Nov 6", + "06-Nov-94", + "Sun Nov 6 94", + "1994.Nov.6", + "Sun/Nov/6/94/GMT", + "Sun, 06 Nov 1994 08:49:37 CET", + "Sun, 06 Nov 1994 08:49:37 cet", + "Sun, 06 Nov 1994 08:49:37 utc", + "Sun, 06 Nov 1994 08:49:37 gmt", + "06 Nov 1994 08:49:37 EST", + "Sun, 06 Nov 1994 08:49:37 UT", + "Sun, 12 Sep 2004 15:05:58 -0700", + "Sat, 11 Sep 2004 21:32:11 +0200", + "20040912 15:05:58 -0700", + "20040911 +0200", + "Thu, 01-Jan-1970 00:59:59 GMT", + "Thu, 01-Jan-1970 01:00:00 GMT", + "Sat, 15-Apr-17 21:01:22 GMT", + "Thu, 19-Apr-2007 16:00:00 GMT", + "Wed, 25 Apr 2007 21:02:13 GMT", + "Thu, 19/Apr\\2007 16:00:00 GMT", + "Fri, 1 Jan 2010 01:01:50 GMT", + "Wednesday, 1-Jan-2003 00:00:00 GMT", + "", + "-", + "a", + "1-Jan-2003 00:00:00 GMT", + "1-Jan-2003 00:00:00 GMT", + "Wed,18-Apr-07 22:50:12 GMT", + "BillyWonka , 18-Apr-07 22:50:12 GMT", + "BillyWonka , 18-Apr-07 22:50:12", + "BillyWonka , 18-apr-07 22:50:12", + "Mon, 18-Apr-1977 22:50:13 GMT", + "Mon, 18-Apr-77 22:50:13 GMT", + "Sat, 15-Apr-17\"21:01:22\"GMT", + "Partyday, 18- April-07 22:50:12", + "Partyday, 18 - Apri-07 22:50:12", + "Wednes, 1-Januar-2003 00:00:00 GMT", + "Sat, 15-Apr-17 21:01:22", + "Sat, 15-Apr-17 21:01:22 GMT-2", + "Sat, 15-Apr-17 21:01:22 GMT BLAH", + "Sat, 15-Apr-17 21:01:22 GMT-0400", + "Sat, 15-Apr-17 21:01:22 GMT-0400 (EDT)", + "Sat, 15-Apr-17 21:01:22 DST", + "Sat, 15-Apr-17 21:01:22 -0400", + "Sat, 15-Apr-17 21:01:22 (hello there)", + "Sat, 15-Apr-17 21:01:22 11:22:33", + "Sat, 15-Apr-17 ::00 21:01:22", + "Sat, 15-Apr-17 boink:z 21:01:22", + "Sat, 15-Apr-17 91:22:33 21:01:22", + "Thu Apr 18 22:50:12 2007 GMT", + "22:50:12 Thu Apr 18 2007 GMT", + "Thu 22:50:12 Apr 18 2007 GMT", + "Thu Apr 22:50:12 18 2007 GMT", + "Thu Apr 18 22:50:12 2007 GMT", + "Thu Apr 18 2007 22:50:12 GMT", + "Thu Apr 18 2007 GMT 22:50:12", + "\"Thu Apr 18 22:50:12 2007 GMT\"", + "-\"22:50:12 Thu Apr 18 2007 GMT\"", + "*\"Thu 22:50:12 Apr 18 2007 GMT\"", + ";\"Thu Apr 22:50:12 18 2007 GMT\"", + ".\"Thu Apr 18 22:50:12 2007 GMT\"", + "\"Thu Apr 18 2007 22:50:12 GMT\"", + "\"Thu Apr 18 2007 GMT 22:50:12\"", + "Sat, 15-Apr-17 21:01:22 GMT", + "15-Sat, Apr-17 21:01:22 GMT", + "15-Sat, Apr 21:01:22 GMT 17", + "15-Sat, Apr 21:01:22 GMT 2017", + "15 Apr 21:01:22 2017", + "15 17 Apr 21:01:22", + "Apr 15 17 21:01:22", + "Apr 15 21:01:22 17", + "2017 April 15 21:01:22", + "15 April 2017 21:01:22", + "98 April 17 21:01:22", + "Thu, 012-Aug-2008 20:49:07 GMT", + "Thu, 999999999999-Aug-2007 20:49:07 GMT", + "Thu, 12-Aug-2007 20:61:99999999999 GMT", + "IAintNoDateFool", + "Thu Apr 18 22:50 2007 GMT", + "20110623 12:34:56", + "20110023 12:34:56", + "20110632 12:34:56", + "20110623 56:34:56", + "20111323 12:34:56", + "20110623 12:34:79", + "Wed, 31 Dec 2008 23:59:60 GMT", + "Wed, 31 Dec 2008 23:59:61 GMT", + "Wed, 31 Dec 2008 24:00:00 GMT", + "Wed, 31 Dec 2008 23:60:59 GMT", + "20110623 12:3", + "20110623 1:3", + "20110623 1:30", + "20110623 12:12:3", + "20110623 01:12:3", + "20110623 01:99:30", + "Thu, 01-Jan-1970 00:00:00 GMT", + "Thu, 31-Dec-1969 23:59:58 GMT", + "Thu, 31-Dec-1969 23:59:59 GMT", + "Sun, 06 Nov 2044 08:49:37 GMT", + "Sun, 06 Nov 3144 08:49:37 GMT", + "Sun, 06 Nov 1900 08:49:37 GMT", + "Sun, 06 Nov 1800 08:49:37 GMT", + "Thu, 01-Jan-1583 00:00:00 GMT", + "Thu, 01-Jan-1499 00:00:00 GMT", + "Sun, 06 Nov 1968 08:49:37 GMT", + "2094 Nov 6 08:49:37", + "01 Jan 2001 8:0:0", + "01 Jan 2001 8:00:0", + /* Out-of-range day-of-month Cases */ + "29 Feb 2023 12:00:00 GMT", + "31 Apr 2024 12:00:00 GMT", + "30 Feb 2024 12:00:00 GMT", + "01-13-2024", + "32 Jan 2024", + "31 Jan 2024", + "32 Feb 2024", + "32 Mar 2024", + "32 Apr 2024", + "32 May 2024", + "32 Jun 2024", + "32 Jul 2024", + "32 Aug 2024", + "32 Sep 2024", + "32 Oct 2024", + "32 Nov 2024", + "32 Dec 2024", + /* Timezone Offsets */ + "Sun, 06 Nov 1994 08:49:37 +0530", + "Sun, 06 Nov 1994 08:49:37 +0545", + "06 Nov 1994 08:49:37 Z", + "06 Nov 1994 08:49:37 A", + "06 Nov 1994 08:49:37 B", + "06 Nov 1994 08:49:37 C", + "06 Nov 1994 08:49:37 D", + "06 Nov 1994 08:49:37 E", + "06 Nov 1994 08:49:37 F", + "06 Nov 1994 08:49:37 G", + "06 Nov 1994 08:49:37 H", + "06 Nov 1994 08:49:37 I", + "06 Nov 1994 08:49:37 J", + "06 Nov 1994 08:49:37 K", + "06 Nov 1994 08:49:37 L", + "06 Nov 1994 08:49:37 M", + "06 Nov 1994 08:49:37 N", + "06 Nov 1994 08:49:37 O", + "06 Nov 1994 08:49:37 P", + "06 Nov 1994 08:49:37 Q", + "06 Nov 1994 08:49:37 R", + "06 Nov 1994 08:49:37 S", + "06 Nov 1994 08:49:37 T", + "06 Nov 1994 08:49:37 U", + "06 Nov 1994 08:49:37 V", + "06 Nov 1994 08:49:37 W", + "06 Nov 1994 08:49:37 X", + "06 Nov 1994 08:49:37 Y", + "GMT+05:30", + "GMT-08:00", + /* ISO 8601 & Variations - not supported */ + "1994-11-06T08:49:37Z", + "1994-11-06 08:49:37.123 GMT", + "19941106T084937Z", + /* Y2K38 & Historical Boundaries */ + "19 Jan 2038 03:14:07 GMT", + "19 Jan 2038 03:14:08 GMT", + "01 Jan 69 00:00:00 GMT", + "01 Jan 1500 00:00:00 GMT", + /* Formatting & Malformed Junk */ + "Sun, 06-Nov/1994 08:49:37", + "Sun, 06 Nov 1994 08:49:37 GMT", + " Sun, 06 Nov 1994 08:49:37 GMT ", + "Date: Sun, 06 Nov 1994 08:49:37 GMT", + /* wrong day name is ignored */ + "Monday, 06 Nov 1994 08:49:37 GMT" +}; + +/* + * Parse the dates + */ +static int test_dateparser(int argc, const char **argv) +{ + struct curltime start; + struct curltime end; + timediff_t us; + long long hn; + curl_off_t loops = 100000, loop; + curl_off_t count = 0; + curl_off_t ecount = 0; /* errors */ + size_t i; + size_t ndates = CURL_ARRAYSIZE(dates); + + if(argc > 1) { + const char *ptr = argv[1]; + curl_off_t num; + if(!curlx_str_number(&ptr, &num, INT_MAX) && !*ptr) + loops = num; + } + + curl_mprintf("Found %zu dates to test\n", ndates); + + start = curlx_now(); + for(loop = 0; loop < loops; loop++) { + for(i = 0; i < ndates; i++) { + time_t out = curl_getdate(dates[i], NULL); + count++; + if(out == (time_t)-1) { +#if 0 + curl_mfprintf(stderr, "Failed [%u]: %s\n", (int)hcode, buffer); +#endif + ecount++; + } + } + } + end = curlx_now(); + us = curlx_timediff_us(end, start); /* how many microseconds */ + hn = count ? us * 100000 / count : 0; /* 100 times too big */ + curl_mprintf("Dates: %" CURL_FORMAT_CURL_OFF_T "\n" + "Time: %lld usecs\n" + "Time/date: %lld.%0lld ns\n" + "Dates/sec: %lld\n" + "Errors: %" CURL_FORMAT_CURL_OFF_T "\n", + count, + (long long)us, + (long long)hn / 100, + (long long)hn % 100, + us ? (long long)(count * 1000000) / us : 0, + ecount); + return 0; +} From c24a69d8cd368de4e25e3540bf0b7b000a31d5a7 Mon Sep 17 00:00:00 2001 From: Joshua Rogers Date: Sat, 12 Sep 2026 02:57:04 +0200 Subject: [PATCH 144/481] tool: buffer headers before remote filename selection Verified by test 3037 and 3038. Closes #22977 --- src/tool_cb_hdr.c | 53 ++++++++++++++++++++++------------ tests/data/Makefile.am | 2 ++ tests/data/test3037 | 62 ++++++++++++++++++++++++++++++++++++++++ tests/data/test3038 | 64 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 163 insertions(+), 18 deletions(-) create mode 100644 tests/data/test3037 create mode 100644 tests/data/test3038 diff --git a/src/tool_cb_hdr.c b/src/tool_cb_hdr.c index 014e4b832ed1..c9bc8978edd8 100644 --- a/src/tool_cb_hdr.c +++ b/src/tool_cb_hdr.c @@ -248,6 +248,28 @@ int tool_write_headers(struct HdrCbData *hdrcbdata, FILE *stream) return rc; } +/* Store a header until the output filename has been decided. */ +static size_t buffer_header(struct HdrCbData *hdrcbdata, + const char *str, size_t cb) +{ + char *clone = curlx_memdup0(str, cb); + if(clone) { + struct curl_slist *old = hdrcbdata->headlist; + hdrcbdata->headlist = curl_slist_append(old, clone); + curlx_free(clone); + if(!hdrcbdata->headlist) { + curl_slist_free_all(old); + return CURL_WRITEFUNC_ERROR; + } + } + else { + curl_slist_free_all(hdrcbdata->headlist); + hdrcbdata->headlist = NULL; + return CURL_WRITEFUNC_ERROR; + } + return cb; +} + /* * Write etag to file when --etag-save option is given. */ @@ -388,24 +410,8 @@ static size_t content_disposition(const char *str, const char *end, if(hdrcbdata->honor_cd_filename && hdrcbdata->config->show_headers) { - /* still awaiting the Content-Disposition header, store the header in - memory. Since it is not null-terminated, we need an extra dance. */ - char *clone = curlx_memdup0(str, cb); - if(clone) { - struct curl_slist *old = hdrcbdata->headlist; - hdrcbdata->headlist = curl_slist_append(old, clone); - curlx_free(clone); - if(!hdrcbdata->headlist) { - curl_slist_free_all(old); - return CURL_WRITEFUNC_ERROR; - } - } - else { - curl_slist_free_all(hdrcbdata->headlist); - hdrcbdata->headlist = NULL; - return CURL_WRITEFUNC_ERROR; - } - return cb; /* done for now */ + /* still awaiting the Content-Disposition header */ + return buffer_header(hdrcbdata, str, cb); } return 0; /* ok */ @@ -427,6 +433,7 @@ size_t tool_header_cb(char *ptr, size_t size, size_t nmemb, void *userdata) const size_t cb = size * nmemb; const char *end = ptr + cb; const char *scheme = NULL; + bool cd_checked = FALSE; if(!per->config) return CURL_WRITEFUNC_ERROR; @@ -486,6 +493,9 @@ size_t tool_header_cb(char *ptr, size_t size, size_t nmemb, void *userdata) size_t rc = content_disposition(str, end, cb, per, response); if(rc) return rc; + /* remember that this was checked to avoid duplicating it further + down */ + cd_checked = TRUE; } } if(hdrcbdata->config->writeout) { @@ -505,6 +515,13 @@ size_t tool_header_cb(char *ptr, size_t size, size_t nmemb, void *userdata) /* bold headers only for selected protocols */ const char *value = NULL; + /* Informational responses, and ETag headers handled by --etag-save, + bypass the content-disposition handling above. Do not open the output + file until the filename decision is complete. Only for HTTP(S). */ + if(hdrcbdata->honor_cd_filename && !cd_checked && + (scheme == proto_http || scheme == proto_https)) + return buffer_header(hdrcbdata, str, cb); + if(!outs->stream && !tool_create_output_file(outs, per->config)) return CURL_WRITEFUNC_ERROR; diff --git a/tests/data/Makefile.am b/tests/data/Makefile.am index 2aa0cac66988..af92dc804d63 100644 --- a/tests/data/Makefile.am +++ b/tests/data/Makefile.am @@ -2049,6 +2049,8 @@ TESTCASES = \ test3034 \ test3035 \ test3036 \ + test3037 \ + test3038 \ \ test3100 \ test3101 \ diff --git a/tests/data/test3037 b/tests/data/test3037 new file mode 100644 index 000000000000..be29b991c12a --- /dev/null +++ b/tests/data/test3037 @@ -0,0 +1,62 @@ + + + + +HTTP +HTTP GET +103 +Early Hints +-J +--show-headers + + + +# Server-side + + +HTTP/1.1 103 Early Hints + +HTTP/1.1 200 OK +Content-Length: 6 +Content-Disposition: attachment; filename="victim%TESTNUMBER" +Connection: close + +12345 + + + +# Client-side + + +http + + +http + + +Do not open output for 103 Early Hints with -J and --show-headers + + +original + + +http://%HOSTIP:%HTTPPORT/victim%TESTNUMBER -O -J -i --output-dir %LOGDIR + + + + + +GET /victim%TESTNUMBER HTTP/1.1 +Host: %HOSTIP:%HTTPPORT +User-Agent: curl/%VERSION +Accept: */* + + + +23 + + +original + + + diff --git a/tests/data/test3038 b/tests/data/test3038 new file mode 100644 index 000000000000..ff51458f54bc --- /dev/null +++ b/tests/data/test3038 @@ -0,0 +1,64 @@ + + + + +HTTP +HTTP GET +ETag +--etag-save +-J +--show-headers + + + +# Server-side + + +HTTP/1.1 200 OK +ETag: "tag%TESTNUMBER" +Content-Length: 6 +Content-Disposition: attachment; filename="victim%TESTNUMBER" +Connection: close + +12345 + + + +# Client-side + + +http + + +http + + +Do not open output for ETag with -J, --show-headers, --etag-save + + +original + + +http://%HOSTIP:%HTTPPORT/victim%TESTNUMBER -O -J -i --etag-save %LOGDIR/etag%TESTNUMBER --output-dir %LOGDIR + + + + + +GET /victim%TESTNUMBER HTTP/1.1 +Host: %HOSTIP:%HTTPPORT +User-Agent: curl/%VERSION +Accept: */* + + + +23 + + +original + + +"tag%TESTNUMBER" + + + From 44dc0ab1880d36dc7655df614827b28a6e54d3bc Mon Sep 17 00:00:00 2001 From: Daniel Stenberg Date: Wed, 16 Sep 2026 13:59:36 +0200 Subject: [PATCH 145/481] httpsig-key.md: correct the generating Ed25519 keys commands Reported-by: Skye Soss Fixes #22961 Closes #22967 --- docs/cmdline-opts/httpsig-key.md | 5 +++-- docs/libcurl/opts/CURLOPT_HTTPSIG_KEY.md | 6 ++++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/docs/cmdline-opts/httpsig-key.md b/docs/cmdline-opts/httpsig-key.md index c0a04051485f..7cd1779593c9 100644 --- a/docs/cmdline-opts/httpsig-key.md +++ b/docs/cmdline-opts/httpsig-key.md @@ -32,7 +32,8 @@ The key is formatted as a series of hexadecimal digits in a single line. For With OpenSSL 3: openssl genpkey -algorithm ED25519 -out k.pem - openssl pkey -in k.pem -outform RAW -out k.raw - xxd -p -c 64 k.raw | tr -d '\n' > k.hex + openssl pkey -in k.pem -text -noout | \ + grep -A3 ^priv: | \ + tr -cd '[:xdigit:]' > k.hex Use `@k.hex` with `--httpsig-key`. diff --git a/docs/libcurl/opts/CURLOPT_HTTPSIG_KEY.md b/docs/libcurl/opts/CURLOPT_HTTPSIG_KEY.md index 1f03de2c26e5..94d65353dd39 100644 --- a/docs/libcurl/opts/CURLOPT_HTTPSIG_KEY.md +++ b/docs/libcurl/opts/CURLOPT_HTTPSIG_KEY.md @@ -43,8 +43,10 @@ PEM and other encodings are not supported; pass the raw key material as hex. With OpenSSL 3: - openssl genpkey -algorithm ED25519 -out ed25519.pem - openssl pkey -in ed25519.pem -outform RAW | xxd -p -c 64 | tr -d '\n' > key.hex + openssl genpkey -algorithm ED25519 -out k.pem + openssl pkey -in k.pem -text -noout | \ + grep -A3 ^priv: | \ + tr -cd '[:xdigit:]' > k.hex The `key.hex` file is one line of 64 hexadecimal digits. From 94378899cea12434745ba2539765beb8f848e431 Mon Sep 17 00:00:00 2001 From: Daniel Stenberg Date: Thu, 17 Sep 2026 12:39:53 +0200 Subject: [PATCH 146/481] perf-tests: output two-digital decimals always Otherwise they display slightly off. Closes #22982 --- tests/perf/base64dec.c | 2 +- tests/perf/base64enc.c | 2 +- tests/perf/dateparser.c | 2 +- tests/perf/maprintf.c | 2 +- tests/perf/simpleformat.c | 2 +- tests/perf/snprintf.c | 2 +- tests/perf/urldecode.c | 2 +- tests/perf/urlencode.c | 2 +- tests/perf/urlparser.c | 2 +- 9 files changed, 9 insertions(+), 9 deletions(-) diff --git a/tests/perf/base64dec.c b/tests/perf/base64dec.c index da4339758054..f023eb3d1866 100644 --- a/tests/perf/base64dec.c +++ b/tests/perf/base64dec.c @@ -63,7 +63,7 @@ static int test_base64dec(int argc, const char **argv) hn = loop ? us * 100000 / loops : 0; /* 100 times too big */ curl_mprintf("Loops: %" CURL_FORMAT_CURL_OFF_T "\n" "Time: %lld usecs\n" - "Time/loop: %lld.%0lld ns\n", + "Time/loop: %lld.%02lld ns\n", loops, (long long)us, (long long)hn / 100, diff --git a/tests/perf/base64enc.c b/tests/perf/base64enc.c index 602799185018..622f956b4986 100644 --- a/tests/perf/base64enc.c +++ b/tests/perf/base64enc.c @@ -63,7 +63,7 @@ static int test_base64enc(int argc, const char **argv) hn = loop ? us * 100000 / loops : 0; /* 100 times too big */ curl_mprintf("Loops: %" CURL_FORMAT_CURL_OFF_T "\n" "Time: %lld usecs\n" - "Time/loop: %lld.%0lld ns\n", + "Time/loop: %lld.%02lld ns\n", loops, (long long)us, (long long)hn / 100, diff --git a/tests/perf/dateparser.c b/tests/perf/dateparser.c index 5d8b3c6c2ead..f2438160a034 100644 --- a/tests/perf/dateparser.c +++ b/tests/perf/dateparser.c @@ -257,7 +257,7 @@ static int test_dateparser(int argc, const char **argv) hn = count ? us * 100000 / count : 0; /* 100 times too big */ curl_mprintf("Dates: %" CURL_FORMAT_CURL_OFF_T "\n" "Time: %lld usecs\n" - "Time/date: %lld.%0lld ns\n" + "Time/date: %lld.%02lld ns\n" "Dates/sec: %lld\n" "Errors: %" CURL_FORMAT_CURL_OFF_T "\n", count, diff --git a/tests/perf/maprintf.c b/tests/perf/maprintf.c index f9527bcf147c..36c3eaa57f21 100644 --- a/tests/perf/maprintf.c +++ b/tests/perf/maprintf.c @@ -66,7 +66,7 @@ static int test_maprintf(int argc, const char **argv) hn = loops ? us * 100000 / loops : 0; /* 100 times too big */ curl_mprintf("Loops: %" CURL_FORMAT_CURL_OFF_T "\n" "Time: %lld usecs\n" - "Time/loop: %lld.%0lld ns\n", + "Time/loop: %lld.%02lld ns\n", loops, (long long)us, (long long)hn / 100, diff --git a/tests/perf/simpleformat.c b/tests/perf/simpleformat.c index ca0051824bc3..a03a755ca4a3 100644 --- a/tests/perf/simpleformat.c +++ b/tests/perf/simpleformat.c @@ -61,7 +61,7 @@ static int test_simpleformat(int argc, const char **argv) hn = loops ? us * 100000 / loops : 0; /* 100 times too big */ curl_mprintf("Loops: %" CURL_FORMAT_CURL_OFF_T "\n" "Time: %lld usecs\n" - "Time/loop: %lld.%0lld ns\n", + "Time/loop: %lld.%02lld ns\n", loops, (long long)us, (long long)hn / 100, diff --git a/tests/perf/snprintf.c b/tests/perf/snprintf.c index 22087e17ce73..12e8e1a2d2cc 100644 --- a/tests/perf/snprintf.c +++ b/tests/perf/snprintf.c @@ -58,7 +58,7 @@ static int test_snprintf(int argc, const char **argv) hn = loops ? us * 100000 / loops : 0; /* 100 times too big */ curl_mprintf("Loops: %" CURL_FORMAT_CURL_OFF_T "\n" "Time: %lld usecs\n" - "Time/loop: %lld.%0lld ns\n", + "Time/loop: %lld.%02lld ns\n", loops, (long long)us, (long long)hn / 100, diff --git a/tests/perf/urldecode.c b/tests/perf/urldecode.c index b4b391e325f3..c37cc8d5c1bf 100644 --- a/tests/perf/urldecode.c +++ b/tests/perf/urldecode.c @@ -76,7 +76,7 @@ static int test_urldecode(int argc, const char **argv) hn = loop ? us * 100000 / loops : 0; /* 100 times too big */ curl_mprintf("Loops: %" CURL_FORMAT_CURL_OFF_T "\n" "Time: %lld usecs\n" - "Time/loop: %lld.%0lld ns\n", + "Time/loop: %lld.%02lld ns\n", loops, (long long)us, (long long)hn / 100, diff --git a/tests/perf/urlencode.c b/tests/perf/urlencode.c index 999e0f25094d..71b9e5fc3df7 100644 --- a/tests/perf/urlencode.c +++ b/tests/perf/urlencode.c @@ -58,7 +58,7 @@ static int test_urlencode(int argc, const char **argv) hn = loop ? us * 100000 / loops : 0; /* 100 times too big */ curl_mprintf("Loops: %" CURL_FORMAT_CURL_OFF_T "\n" "Time: %lld usecs\n" - "Time/loop: %lld.%0lld ns\n", + "Time/loop: %lld.%02lld ns\n", loops, (long long)us, (long long)hn / 100, diff --git a/tests/perf/urlparser.c b/tests/perf/urlparser.c index 934e6770809d..594830a8cde0 100644 --- a/tests/perf/urlparser.c +++ b/tests/perf/urlparser.c @@ -1172,7 +1172,7 @@ static int test_urlparser(int argc, const char **argv) hn = count ? us * 100000 / count : 0; /* 100 times too big */ curl_mprintf("URLs: %" CURL_FORMAT_CURL_OFF_T "\n" "Time: %lld usecs\n" - "Time/URL: %lld.%0lld ns\n" + "Time/URL: %lld.%02lld ns\n" "URLs/sec: %lld\n" "Errors: %" CURL_FORMAT_CURL_OFF_T "\n", count, From 7da2175b6b8e011da97e0a4fcf41b1bcd7a3e275 Mon Sep 17 00:00:00 2001 From: Stefan Eissing Date: Thu, 17 Sep 2026 12:49:16 +0200 Subject: [PATCH 147/481] u8_strset: rename and mask Move `uint-hashset.[ch]` to `u8_strset.[ch]` for clarity. Add `uint8_t slotmask` in struct to keep precomputed mask instead of array lookups. New member fits in "hole", struct size stays unchanged. Closes #22983 --- lib/Makefile.inc | 4 +-- lib/{uint-hashset.c => u8_strset.c} | 41 +++++++++++------------------ lib/{uint-hashset.h => u8_strset.h} | 7 ++--- lib/urldata.h | 2 +- tests/data/test3211 | 3 ++- tests/unit/unit3211.c | 6 ++--- 6 files changed, 28 insertions(+), 35 deletions(-) rename lib/{uint-hashset.c => u8_strset.c} (90%) rename lib/{uint-hashset.h => u8_strset.h} (96%) diff --git a/lib/Makefile.inc b/lib/Makefile.inc index a36cc8611aa3..301708da23ef 100644 --- a/lib/Makefile.inc +++ b/lib/Makefile.inc @@ -288,9 +288,9 @@ LIB_CFILES = \ thrdpool.c \ thrdqueue.c \ transfer.c \ + u8_strset.c \ uint-bset.c \ uint-hash.c \ - uint-hashset.c \ uint-spbset.c \ uint-table.c \ url.c \ @@ -420,9 +420,9 @@ LIB_HFILES = \ thrdpool.h \ thrdqueue.h \ transfer.h \ + u8_strset.h \ uint-bset.h \ uint-hash.h \ - uint-hashset.h \ uint-spbset.h \ uint-table.h \ url.h \ diff --git a/lib/uint-hashset.c b/lib/u8_strset.c similarity index 90% rename from lib/uint-hashset.c rename to lib/u8_strset.c index 6f2fbd5fb594..be1148c4442e 100644 --- a/lib/uint-hashset.c +++ b/lib/u8_strset.c @@ -23,7 +23,7 @@ ***************************************************************************/ #include "curl_setup.h" -#include "uint-hashset.h" +#include "u8_strset.h" #include "curlx/strdup.h" /* random patterns for API verification */ @@ -35,21 +35,10 @@ #define CURL_SWAP(a, b) (((a) ^= (b)), ((b) ^= (a)), ((a) ^= (b))) -static const uint8_t u8_smask[] = { - 0x00U, - 0x01U, - 0x03U, - 0x07U, - 0x0FU, - 0x1FU, - 0x3FU, - 0x7FU, - 0xFFU, -}; - -#define CURL_U8_SET_SLOT_IDX(s, i) (uint8_t)((i) & u8_smask[(s)->slotbits]) -#define CURL_U8_SLOT_CNT(i) ((uint16_t)u8_smask[(i)] + 1) -#define CURL_U8_SET_SLOT_CNT(s) CURL_U8_SLOT_CNT((s)->slotbits) +#define CURL_U8_STRSET_SLOT_IDX(s, i) (uint8_t)((i) & (s)->slotmask) +#define CURL_U8_SLOT_CNT(i) (uint16_t)((i) ? (1U << (i)) : 0) +#define CURL_U8_SLOT_MASK(i) (uint8_t)((1U << (i)) - 1) +#define CURL_U8_STRSET_SLOT_CNT(s) CURL_U8_SLOT_CNT((s)->slotbits) /* A hashset for tuples (id, string) using Robin Hood Hashing. * @@ -85,6 +74,7 @@ void Curl_u8_strset_init(struct u8_strset *set) set->ids = set->sids; set->psl = set->spsl; set->slotbits = CURL_U8_STRSET_START_BITS; + set->slotmask = CURL_U8_SLOT_MASK(set->slotbits); set->count = 0; #ifdef DEBUGBUILD set->init = CURL_U8_STRSET_MAGIC; @@ -95,7 +85,7 @@ void Curl_u8_strset_clear(struct u8_strset *set) { uint16_t i; DEBUGASSERT(set->init == CURL_U8_STRSET_MAGIC); - for(i = 0; i < CURL_U8_SET_SLOT_CNT(set); ++i) + for(i = 0; i < CURL_U8_STRSET_SLOT_CNT(set); ++i) curlx_safefree(set->data[i]); if(set->data != set->sdata) @@ -105,7 +95,7 @@ void Curl_u8_strset_clear(struct u8_strset *set) static void u8_strset_addn(struct u8_strset *set, uint8_t id, char *val) { - uint8_t i = CURL_U8_SET_SLOT_IDX(set, id); + uint8_t i = CURL_U8_STRSET_SLOT_IDX(set, id); uint8_t psl = 0; while(set->data[i]) { if(psl > set->psl[i]) { /* SWAP */ @@ -116,7 +106,7 @@ static void u8_strset_addn(struct u8_strset *set, uint8_t id, char *val) CURL_SWAP(set->psl[i], psl); CURL_SWAP(set->ids[i], id); } - i = CURL_U8_SET_SLOT_IDX(set, i + 1); + i = CURL_U8_STRSET_SLOT_IDX(set, i + 1); ++psl; } set->ids[i] = id; @@ -159,6 +149,7 @@ static bool u8_strset_grow(struct u8_strset *set) set->ids = (uint8_t *)d + (nslots * sizeof(char *)); set->psl = set->ids + nslots; set->slotbits = nslotbits; + set->slotmask = CURL_U8_SLOT_MASK(set->slotbits); set->count = 0; /* re-add previous entries */ for(i = 0; i < CURL_U8_SLOT_CNT(prev_slots); ++i) { @@ -173,7 +164,7 @@ static bool u8_strset_grow(struct u8_strset *set) static bool u8_strset_get_index(struct u8_strset *set, uint8_t id, uint8_t *pindex) { - uint8_t i = CURL_U8_SET_SLOT_IDX(set, id); + uint8_t i = CURL_U8_STRSET_SLOT_IDX(set, id); uint8_t psl = 0; while(set->data[i] && (psl <= set->psl[i])) { if(set->ids[i] == id) { @@ -183,7 +174,7 @@ static bool u8_strset_get_index(struct u8_strset *set, #endif return TRUE; } - i = CURL_U8_SET_SLOT_IDX(set, i + 1); + i = CURL_U8_STRSET_SLOT_IDX(set, i + 1); ++psl; } #if CURL_U8_STRSET_DEBUG @@ -231,7 +222,7 @@ CURLcode Curl_u8_strset_setn(struct u8_strset *set, return CURLE_OK; } /* `id` not in set yet, grow if full */ - if((set->count >= CURL_U8_SET_SLOT_CNT(set)) && !u8_strset_grow(set)) { + if((set->count >= CURL_U8_STRSET_SLOT_CNT(set)) && !u8_strset_grow(set)) { curlx_free(str); return CURLE_OUT_OF_MEMORY; } @@ -278,7 +269,7 @@ static void u8_strset_unset(struct u8_strset *set, uint8_t id, bool zero) curlx_safefree(set->data[i]); set->ids[i] = set->psl[i] = 0; --set->count; - j = CURL_U8_SET_SLOT_IDX(set, i + 1); + j = CURL_U8_STRSET_SLOT_IDX(set, i + 1); /* shift all entries with positive psl "down" */ while(set->data[j] && set->psl[j]) { set->data[i] = set->data[j]; @@ -287,7 +278,7 @@ static void u8_strset_unset(struct u8_strset *set, uint8_t id, bool zero) set->data[j] = NULL; set->ids[j] = set->psl[j] = 0; i = j; - j = CURL_U8_SET_SLOT_IDX(set, i + 1); + j = CURL_U8_STRSET_SLOT_IDX(set, i + 1); } } } @@ -309,7 +300,7 @@ CURLcode Curl_u8_strset_copy(struct u8_strset *dest, struct u8_strset *src) DEBUGASSERT(src->init == CURL_U8_STRSET_MAGIC); Curl_u8_strset_clear(dest); - for(i = 0; !result && (i < CURL_U8_SET_SLOT_CNT(src)); ++i) { + for(i = 0; !result && (i < CURL_U8_STRSET_SLOT_CNT(src)); ++i) { if(src->data[i]) result = u8_strset_set(dest, src->ids[i], src->data[i]); } diff --git a/lib/uint-hashset.h b/lib/u8_strset.h similarity index 96% rename from lib/uint-hashset.h rename to lib/u8_strset.h index 04352ac7c2f7..bf23cd8521f8 100644 --- a/lib/uint-hashset.h +++ b/lib/u8_strset.h @@ -1,5 +1,5 @@ -#ifndef HEADER_CURL_UINT_HASHSET_H -#define HEADER_CURL_UINT_HASHSET_H +#ifndef HEADER_CURL_U8_STRSET_H +#define HEADER_CURL_U8_STRSET_H /*************************************************************************** * _ _ ____ _ * Project ___| | | | _ \| | @@ -53,6 +53,7 @@ struct u8_strset { uint8_t spsl[CURL_U8_STRSET_START_DIM]; uint16_t count; uint8_t slotbits; + uint8_t slotmask; #ifdef DEBUGBUILD int32_t init; #endif @@ -80,4 +81,4 @@ void Curl_u8_strset_unset0(struct u8_strset *set, uint8_t id); CURLcode Curl_u8_strset_copy(struct u8_strset *dest, struct u8_strset *src); -#endif /* HEADER_CURL_UINT_HASHSET_H */ +#endif /* HEADER_CURL_U8_STRSET_H */ diff --git a/lib/urldata.h b/lib/urldata.h index f0c7af13020c..fe2e3f2c36bf 100644 --- a/lib/urldata.h +++ b/lib/urldata.h @@ -69,7 +69,7 @@ #include "request.h" #include "ratelimit.h" #include "netrc.h" -#include "uint-hashset.h" +#include "u8_strset.h" #include "vdns/asyn.h" #include "vdns/hostip.h" #include "vtls/vtls_config.h" diff --git a/tests/data/test3211 b/tests/data/test3211 index 1c2643abc143..9be8eced05d7 100644 --- a/tests/data/test3211 +++ b/tests/data/test3211 @@ -4,6 +4,7 @@ unittest uint_bset +u8_strset @@ -13,7 +14,7 @@ uint_bset unittest -uint_bset and uint_hashset unit tests +uint_bset and u8_strset unit tests diff --git a/tests/unit/unit3211.c b/tests/unit/unit3211.c index d3f4443eb0ad..3f2aee891816 100644 --- a/tests/unit/unit3211.c +++ b/tests/unit/unit3211.c @@ -24,7 +24,7 @@ #include "unitcheck.h" #include "urldata.h" #include "uint-bset.h" -#include "uint-hashset.h" +#include "u8_strset.h" #include "curl_trc.h" static void t3211_check_bset(const char *name, uint32_t capacity, @@ -130,7 +130,7 @@ static bool t3211_strcmp(const char *s1, const char *s2) return s1 == s2; } -static void t3211_check_strset1(void) +static void t3211_check_u8_strset(void) { struct u8_strset set; char buf[128]; @@ -243,7 +243,7 @@ static CURLcode test_unit3211(const char *arg) t3211_check_bset("s1", 100, s1, CURL_ARRAYSIZE(s1)); t3211_check_bset("s2", 1000, s2, CURL_ARRAYSIZE(s2)); - t3211_check_strset1(); + t3211_check_u8_strset(); UNITTEST_END_SIMPLE } From 7c43ea3101bf91d0503485f682102111cbdbfc40 Mon Sep 17 00:00:00 2001 From: Stefan Eissing Date: Wed, 16 Sep 2026 11:45:28 +0200 Subject: [PATCH 148/481] multi_ntfy: fix handling of more than 128 notifications in a batch When more than 128 notifications have queued up, e.g. more than one chunk is used, the iteration never reaches the second chunk and leaks its and subsequent chunks memory. Reported-By: Fengxiaoxx on github Fixes #22963 Closes #22965 --- lib/multi_ntfy.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/multi_ntfy.c b/lib/multi_ntfy.c index 991259dadfb3..2d30ea8d64c3 100644 --- a/lib/multi_ntfy.c +++ b/lib/multi_ntfy.c @@ -61,7 +61,7 @@ static void mnfty_chunk_destroy(struct mntfy_chunk *chunk) static void mnfty_chunk_reset(struct mntfy_chunk *chunk) { - memset(chunk, 0, sizeof(*chunk)); + chunk->r_offset = chunk->w_offset = 0; } static bool mntfy_chunk_append(struct mntfy_chunk *chunk, From e0dbccd64fa9dd0cd3eaa97a7dbcff3c4cefa9a6 Mon Sep 17 00:00:00 2001 From: Stefan Eissing Date: Thu, 17 Sep 2026 09:36:17 +0200 Subject: [PATCH 149/481] pytests: ftp wildcard matching Add test_30_13 using new debug env var CURL_DBG_FTP_WILDCARD to trigger CURLOPT_WILDCARDMATCH for the curl tool. Closes #22979 --- docs/libcurl/libcurl-env-dbg.md | 4 ++++ lib/transfer.c | 4 ++++ tests/http/test_30_vsftpd.py | 42 +++++++++++++++++++++++++-------- 3 files changed, 40 insertions(+), 10 deletions(-) diff --git a/docs/libcurl/libcurl-env-dbg.md b/docs/libcurl/libcurl-env-dbg.md index 08d6eda3fdbf..49741ce5a21b 100644 --- a/docs/libcurl/libcurl-env-dbg.md +++ b/docs/libcurl/libcurl-env-dbg.md @@ -228,3 +228,7 @@ Existence of this variable suppresses the collection of CONNECT headers. ## `CURL_DBG_MAX_TOTAL_CONNECTIONS` Set the `max_total_connections` for a `multi` handle. + +## `CURL_DBG_FTP_WILDCARD` + +Simulate CURLOPT_WILDCARDMATCH being set. diff --git a/lib/transfer.c b/lib/transfer.c index 3c3ad04c2ec2..5fe962fa0acd 100644 --- a/lib/transfer.c +++ b/lib/transfer.c @@ -564,6 +564,10 @@ CURLcode Curl_pretransfer(struct Curl_easy *data) #ifndef CURL_DISABLE_FTP data->state.wildcardmatch = data->set.wildcard_enabled; +#ifdef DEBUGBUILD + if(getenv("CURL_DBG_FTP_WILDCARD")) + data->state.wildcardmatch = TRUE; +#endif if(data->state.wildcardmatch) { struct WildcardData *wc; if(!data->wildcard) { diff --git a/tests/http/test_30_vsftpd.py b/tests/http/test_30_vsftpd.py index 860c38d5d3f4..9f76ce9a0774 100644 --- a/tests/http/test_30_vsftpd.py +++ b/tests/http/test_30_vsftpd.py @@ -49,9 +49,11 @@ def _make_docs_file(self, docs_dir: str, fname: str, fsize: int): data1k = 1024*'x' flen = 0 with open(fpath, 'w') as fd: + fd.write(f'{fname}--->\n') while flen < fsize: fd.write(data1k) flen += len(data1k) + fd.write(f'<---{fname}\n') return flen @pytest.fixture(autouse=True, scope='class') @@ -244,20 +246,40 @@ def test_30_12_upload_eprt(self, env: Env, vsftpd: VsFTPD): dstfile = os.path.join(vsftpd.docs_dir, docname) assert os.path.exists(dstfile), f'{r.dump_logs()}' + @pytest.mark.skipif(condition=not Env.curl_is_debug(), reason="needs curl debug") + def test_30_13_wildcard(self, env: Env, vsftpd: VsFTPD): + run_env = os.environ.copy() + run_env['CURL_DBG_FTP_WILDCARD'] = '1' + curl = CurlClient(env=env, run_env=run_env) + url = f'ftp://{env.ftp_domain}:{vsftpd.port}/data*0k' + r = curl.ftp_get(urls=[url], with_stats=True) + r.check_stats(count=1, http_status=0) + reffile = os.path.join(env.gen_dir, 'test-30-13.reference') + with open(reffile, "w") as fd: + with open(os.path.join(vsftpd.docs_dir, 'data-0k')) as fd2: + fd.writelines(fd2.readlines()) + with open(os.path.join(vsftpd.docs_dir, 'data-10k')) as fd2: + fd.writelines(fd2.readlines()) + dfile = os.path.join(curl.run_dir, 'download_#1.data') + self.check_download(reffile, dfile) + def check_downloads(self, client, srcfile: str, count: int, complete: bool = True): for i in range(count): dfile = client.download_file(i) - assert os.path.exists(dfile) - if complete and not filecmp.cmp(srcfile, dfile, shallow=False): - with open(srcfile) as fa, open(dfile) as fb: - a = fa.readlines() - b = fb.readlines() - diff = "".join(difflib.unified_diff(a=a, b=b, - fromfile=srcfile, - tofile=dfile, - n=1)) - assert False, f'download {dfile} differs:\n{diff}' + self.check_download(srcfile, dfile, complete) + + def check_download(self, reffile: str, dfile: str, complete: bool = True): + assert os.path.exists(dfile) + if complete and not filecmp.cmp(reffile, dfile, shallow=False): + with open(reffile) as fa, open(dfile) as fb: + a = fa.readlines() + b = fb.readlines() + diff = "".join(difflib.unified_diff(a=a, b=b, + fromfile=reffile, + tofile=dfile, + n=1)) + assert False, f'download {dfile} differs:\n{diff}' def check_upload(self, env, vsftpd: VsFTPD, docname, binary=True): srcfile = os.path.join(env.gen_dir, docname) From 540ee5b560cc6e775e11317048a13cc7e355bf91 Mon Sep 17 00:00:00 2001 From: Samuel Henrique Date: Tue, 15 Sep 2026 18:31:04 -0700 Subject: [PATCH 150/481] tests: add test cases for --styled-output The bold header names and the Location: hyperlink were not covered by any test case, since the test suite could not make curl believe that the output goes to a terminal in the header callback. Make the debug-only CURL_ISATTY environment variable set the tool-wide isatty flag when the transfer is created, instead of only affecting the binary output check in the write callback. The write callback already uses that flag. Test 1425 and 1426 use CURL_ISATTY to check the binary output protection, so they now use --no-styled-output to keep their output plain. The styled test does not run on Windows, where curl only uses styled output when stdout is a console with virtual terminal processing enabled and never writes the hyperlinks. It sets VTE_VERSION so that the hyperlink is written regardless of the terminal running the tests. Closes #22937 --- src/tool_cb_wrt.c | 8 ----- src/tool_operate.c | 14 ++++++++- tests/data/Makefile.am | 2 ++ tests/data/test1425 | 2 +- tests/data/test1426 | 2 +- tests/data/test1689 | 68 ++++++++++++++++++++++++++++++++++++++++++ tests/data/test1690 | 66 ++++++++++++++++++++++++++++++++++++++++ 7 files changed, 151 insertions(+), 11 deletions(-) create mode 100644 tests/data/test1689 create mode 100644 tests/data/test1690 diff --git a/src/tool_cb_wrt.c b/src/tool_cb_wrt.c index 6d18993bf48a..15e5dceb24ce 100644 --- a/src/tool_cb_wrt.c +++ b/src/tool_cb_wrt.c @@ -303,14 +303,6 @@ size_t tool_write_cb(char *buffer, size_t sz, size_t nmemb, void *userdata) return bytes; #ifdef DEBUGBUILD - { - char *tty = curl_getenv("CURL_ISATTY"); - if(tty) { - is_tty = TRUE; - curl_free(tty); - } - } - if(config->show_headers) { if(bytes > (size_t)CURL_MAX_HTTP_HEADER) { warnf("Header data size exceeds write limit"); diff --git a/src/tool_operate.c b/src/tool_operate.c index be69c4bde54d..8256dfa14588 100644 --- a/src/tool_operate.c +++ b/src/tool_operate.c @@ -1339,6 +1339,18 @@ static CURLcode setup_transfer_upload(struct OperationConfig *config, return result; } +/* returns TRUE if the given stream is a terminal */ +static bool stream_isatty(FILE *stream) +{ +#ifdef DEBUGBUILD + /* the test suite sets CURL_ISATTY to make curl act as if the output goes + to a terminal */ + if(getenv("CURL_ISATTY")) + return TRUE; +#endif + return !!isatty(fileno(stream)); +} + /* create a transfer */ static CURLcode create_single(struct OperationConfig *config, CURLSH *share, struct State *state, @@ -1441,7 +1453,7 @@ static CURLcode create_single(struct OperationConfig *config, return result; if(!outs->out_null && output_expected(per->url, per->uploadfile) && - outs->stream && isatty(fileno(outs->stream))) + outs->stream && stream_isatty(outs->stream)) /* we send the output to a tty, therefore we switch off the progress meter */ per->noprogress = global->noprogress = global->isatty = TRUE; diff --git a/tests/data/Makefile.am b/tests/data/Makefile.am index af92dc804d63..9985fa80993e 100644 --- a/tests/data/Makefile.am +++ b/tests/data/Makefile.am @@ -1707,6 +1707,8 @@ TESTCASES = \ test1686 \ test1687 \ test1688 \ + test1689 \ + test1690 \ \ test1700 \ test1702 \ diff --git a/tests/data/test1425 b/tests/data/test1425 index 915ea58870b8..cb47efeaa574 100644 --- a/tests/data/test1425 +++ b/tests/data/test1425 @@ -42,7 +42,7 @@ CURL_ISATTY=yes Refuse binary output to terminal -http://%HOSTIP:%HTTPPORT/%TESTNUMBER +http://%HOSTIP:%HTTPPORT/%TESTNUMBER --no-styled-output diff --git a/tests/data/test1426 b/tests/data/test1426 index 02669a06a0b8..ebd500806fa2 100644 --- a/tests/data/test1426 +++ b/tests/data/test1426 @@ -42,7 +42,7 @@ CURL_ISATTY=yes Okay binary output to terminal -http://%HOSTIP:%HTTPPORT/%TESTNUMBER --output - +http://%HOSTIP:%HTTPPORT/%TESTNUMBER --output - --no-styled-output diff --git a/tests/data/test1689 b/tests/data/test1689 new file mode 100644 index 000000000000..a1e6f5dd528a --- /dev/null +++ b/tests/data/test1689 @@ -0,0 +1,68 @@ + + + + +HTTP +HTTP GET +--styled-output + + + +# +# Server-side + + +HTTP/1.1 301 Moved Permanently +Date: Tue, 09 Nov 2010 14:49:00 GMT +Location: /moved/here +Content-Length: 6 +Connection: close + +-foo- + + + +# +# Client-side + + +http + +# this test needs to be a Debug build in order to fake it being a TTY output + +Debug +!win32 + + +VTE_VERSION=4802 +CURL_ISATTY=yes + + +--styled-output makes header names bold and Location a link + + +http://%HOSTIP:%HTTPPORT/%TESTNUMBER --show-headers + + + +# +# Verify data after the test has been "shot" + + +GET /%TESTNUMBER HTTP/1.1 +Host: %HOSTIP:%HTTPPORT +User-Agent: curl/%VERSION +Accept: */* + + + +HTTP/1.1 301 Moved Permanently +%hex[%1b]hex%[1mDate%hex[%1b]hex%[0m: Tue, 09 Nov 2010 14:49:00 GMT +%hex[%1b]hex%[1mLocation%hex[%1b]hex%[0m: %hex[%1b]hex%]8;;http://%HOSTIP:%HTTPPORT/moved/here%hex[%1b%5c]hex%/moved/here +%hex[%1b]hex%]8;;%hex[%1b%5c]hex%%hex[%1b]hex%[1mContent-Length%hex[%1b]hex%[0m: 6 +%hex[%1b]hex%[1mConnection%hex[%1b]hex%[0m: close + +-foo- + + + diff --git a/tests/data/test1690 b/tests/data/test1690 new file mode 100644 index 000000000000..f3d712faebd7 --- /dev/null +++ b/tests/data/test1690 @@ -0,0 +1,66 @@ + + + + +HTTP +HTTP GET +--styled-output + + + +# +# Server-side + + +HTTP/1.1 301 Moved Permanently +Date: Tue, 09 Nov 2010 14:49:00 GMT +Location: /moved/here +Content-Length: 6 +Connection: close + +-foo- + + + +# +# Client-side + + +http + +# this test needs to be a Debug build in order to fake it being a TTY output + +Debug + + +CURL_ISATTY=yes + + +--no-styled-output writes plain headers to a terminal + + +http://%HOSTIP:%HTTPPORT/%TESTNUMBER --show-headers --no-styled-output + + + +# +# Verify data after the test has been "shot" + + +GET /%TESTNUMBER HTTP/1.1 +Host: %HOSTIP:%HTTPPORT +User-Agent: curl/%VERSION +Accept: */* + + + +HTTP/1.1 301 Moved Permanently +Date: Tue, 09 Nov 2010 14:49:00 GMT +Location: /moved/here +Content-Length: 6 +Connection: close + +-foo- + + + From b85acacec9b38442741bb019bc992e77b5f665d4 Mon Sep 17 00:00:00 2001 From: Daniel Stenberg Date: Thu, 17 Sep 2026 14:32:52 +0200 Subject: [PATCH 151/481] ftplistparser: only use the list parser in CURLWC_MATCHING state Follow-up to ba8f8a7f27a5c5e1 Closes #22985 --- lib/ftplistparser.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/ftplistparser.c b/lib/ftplistparser.c index 2ee5fe3faf9d..77f90c763d8a 100644 --- a/lib/ftplistparser.c +++ b/lib/ftplistparser.c @@ -1017,7 +1017,7 @@ bool Curl_ftp_listmode(struct Curl_easy *data) { if(data->state.wildcardmatch) { struct WildcardData * const wildcard = data->wildcard; - return wildcard && (wildcard->state <= CURLWC_MATCHING); + return wildcard && (wildcard->state == CURLWC_MATCHING); } return FALSE; } From c427e91ddea7c1d4b2f5c54ab4b6810f61483928 Mon Sep 17 00:00:00 2001 From: Ilias Aberkane Date: Thu, 17 Sep 2026 11:01:01 +0200 Subject: [PATCH 152/481] mime: bound Curl_mime_prepare_headers() recursion depth The request-preparation path recursed once per nested multipart level with no depth limit, so a deeply nested mime tree built by the application exhausted the stack inside curl_easy_perform(). Thread a call_depth counter through the internal helper, bounded by MAX_MIME_LEVELS. Deep trees now fail header preparation with CURLE_TOO_LARGE instead of crashing. Since header preparation always precedes the read paths, the depth checks in the mime read functions become redundant and are removed. Verify with test 3596: deeply nested mime trees fail with CURLE_TOO_LARGE instead of exhausting the stack. Fixes #22971 Reported-by: Fengxiaoxx on github Closes #22980 --- lib/mime.c | 69 +++++++++++++++---------------- tests/data/Makefile.am | 1 + tests/data/test3596 | 36 ++++++++++++++++ tests/libtest/Makefile.inc | 1 + tests/libtest/lib3596.c | 84 ++++++++++++++++++++++++++++++++++++++ 5 files changed, 154 insertions(+), 37 deletions(-) create mode 100644 tests/data/test3596 create mode 100644 tests/libtest/lib3596.c diff --git a/lib/mime.c b/lib/mime.c index 3522679a16e2..357152f5bc0f 100644 --- a/lib/mime.c +++ b/lib/mime.c @@ -50,8 +50,7 @@ struct Curl_easy; #define STOP_FILLING ((size_t)-2) static size_t mime_subparts_read(char *buffer, size_t size, size_t nitems, - void *instream, bool *hasread, - size_t call_depth); + void *instream, bool *hasread); static curl_off_t mime_size(curl_mimepart *part); /* Quoted-printable character class table. @@ -701,14 +700,10 @@ static size_t readback_bytes(struct mime_state *state, /* Read a non-encoded part content. */ static size_t read_part_content(curl_mimepart *part, char *buffer, - size_t bufsize, bool *hasread, - size_t call_depth) + size_t bufsize, bool *hasread) { size_t sz = 0; - if(++call_depth > MAX_MIME_LEVELS) - return READ_ERROR; - switch(part->lastreadstatus) { case 0: case CURL_READFUNC_ABORT: @@ -731,8 +726,7 @@ static size_t read_part_content(curl_mimepart *part, char *buffer, * Cannot be processed as other kinds since read function requires * an additional parameter and is highly recursive. */ - sz = mime_subparts_read(buffer, 1, bufsize, part->arg, hasread, - call_depth); + sz = mime_subparts_read(buffer, 1, bufsize, part->arg, hasread); break; case MIMEKIND_FILE: if(part->fp && feof(part->fp)) @@ -771,17 +765,13 @@ static size_t read_part_content(curl_mimepart *part, char *buffer, /* Read and encode part content. */ static size_t read_encoded_part_content(curl_mimepart *part, char *buffer, - size_t bufsize, bool *hasread, - size_t call_depth) + size_t bufsize, bool *hasread) { struct mime_encoder_state *st = &part->encstate; size_t cursize = 0; size_t sz; bool ateof = FALSE; - if(++call_depth > MAX_MIME_LEVELS) - return READ_ERROR; - for(;;) { if(st->bufbeg < st->bufend || ateof) { /* Encode buffered data. */ @@ -814,7 +804,7 @@ static size_t read_encoded_part_content(curl_mimepart *part, char *buffer, if(st->bufend >= sizeof(st->buf)) return cursize ? cursize : READ_ERROR; /* Buffer full. */ sz = read_part_content(part, st->buf + st->bufend, - sizeof(st->buf) - st->bufend, hasread, call_depth); + sizeof(st->buf) - st->bufend, hasread); switch(sz) { case 0: ateof = TRUE; @@ -835,14 +825,10 @@ static size_t read_encoded_part_content(curl_mimepart *part, char *buffer, /* Readback a mime part. */ static size_t readback_part(curl_mimepart *part, - char *buffer, size_t bufsize, bool *hasread, - size_t call_depth) + char *buffer, size_t bufsize, bool *hasread) { size_t cursize = 0; - if(++call_depth > MAX_MIME_LEVELS) - return READ_ERROR; - /* Readback from part. */ while(bufsize) { size_t sz = 0; @@ -886,10 +872,9 @@ static size_t readback_part(curl_mimepart *part, break; case MIMESTATE_CONTENT: if(part->encoder) - sz = read_encoded_part_content(part, buffer, bufsize, hasread, - call_depth); + sz = read_encoded_part_content(part, buffer, bufsize, hasread); else - sz = read_part_content(part, buffer, bufsize, hasread, call_depth); + sz = read_part_content(part, buffer, bufsize, hasread); switch(sz) { case 0: mimesetstate(&part->state, MIMESTATE_END, NULL); @@ -923,16 +908,12 @@ static size_t readback_part(curl_mimepart *part, /* Readback from mime. Warning: not a read callback function. */ static size_t mime_subparts_read(char *buffer, size_t size, size_t nitems, - void *instream, bool *hasread, - size_t call_depth) + void *instream, bool *hasread) { curl_mime *mime = (curl_mime *)instream; size_t cursize = 0; (void)size; /* Always 1 */ - if(++call_depth > MAX_MIME_LEVELS) - return READ_ERROR; - while(nitems) { size_t sz = 0; curl_mimepart *part = mime->state.ptr; @@ -967,7 +948,7 @@ static size_t mime_subparts_read(char *buffer, size_t size, size_t nitems, mimesetstate(&mime->state, MIMESTATE_END, NULL); break; } - sz = readback_part(part, buffer, nitems, hasread, call_depth); + sz = readback_part(part, buffer, nitems, hasread); switch(sz) { case CURL_READFUNC_ABORT: case CURL_READFUNC_PAUSE: @@ -1551,7 +1532,7 @@ size_t Curl_mime_read(char *buffer, size_t size, size_t nitems, void *instream) * adding any data and this loops infinitely. */ do { hasread = FALSE; - ret = readback_part(part, buffer, nitems, &hasread, 0); + ret = readback_part(part, buffer, nitems, &hasread); /* * If this is not possible to get some data without calling more than * one read callback (probably because a content encoder is not able to @@ -1774,11 +1755,12 @@ static CURLcode add_content_disposition(struct Curl_easy *data, return CURLE_OK; } -CURLcode Curl_mime_prepare_headers(struct Curl_easy *data, - curl_mimepart *part, - const char *contenttype, - const char *disposition, - enum mimestrategy strategy) +static CURLcode mime_prepare_headers(struct Curl_easy *data, + curl_mimepart *part, + const char *contenttype, + const char *disposition, + enum mimestrategy strategy, + size_t call_depth) { curl_mime *mime = NULL; const char *boundary = NULL; @@ -1786,6 +1768,9 @@ CURLcode Curl_mime_prepare_headers(struct Curl_easy *data, const char *cte = NULL; CURLcode result = CURLE_OK; + if(++call_depth > MAX_MIME_LEVELS) + return CURLE_TOO_LARGE; + /* Get rid of previously prepared headers. */ curl_slist_free_all(part->curlheaders); part->curlheaders = NULL; @@ -1874,8 +1859,8 @@ CURLcode Curl_mime_prepare_headers(struct Curl_easy *data, if(content_type_match(contenttype, STRCONST("multipart/form-data"))) disposition = "form-data"; for(subpart = mime->firstpart; subpart; subpart = subpart->nextpart) { - result = Curl_mime_prepare_headers(data, subpart, NULL, - disposition, strategy); + result = mime_prepare_headers(data, subpart, NULL, + disposition, strategy, call_depth); if(result) return result; } @@ -1883,6 +1868,16 @@ CURLcode Curl_mime_prepare_headers(struct Curl_easy *data, return result; } +CURLcode Curl_mime_prepare_headers(struct Curl_easy *data, + curl_mimepart *part, + const char *contenttype, + const char *disposition, + enum mimestrategy strategy) +{ + return mime_prepare_headers(data, part, contenttype, disposition, + strategy, 0); +} + /* Recursively reset paused status in the given part. */ static void mime_unpause(curl_mimepart *part) { diff --git a/tests/data/Makefile.am b/tests/data/Makefile.am index 9985fa80993e..f5eef86be69d 100644 --- a/tests/data/Makefile.am +++ b/tests/data/Makefile.am @@ -2110,6 +2110,7 @@ TESTCASES = \ test3500 \ test3555 \ test3595 \ + test3596 \ test4000 \ test4001 \ test4644 \ diff --git a/tests/data/test3596 b/tests/data/test3596 new file mode 100644 index 000000000000..2b55123fffba --- /dev/null +++ b/tests/data/test3596 @@ -0,0 +1,36 @@ + + + + +mime + + + +# Server-side + + + + + +# Client-side + + +Mime + + +http + + +lib%TESTNUMBER + + +Curl_mime_prepare_headers + + +http://%HOSTIP:%HTTPPORT/%TESTNUMBER + + + + + + diff --git a/tests/libtest/Makefile.inc b/tests/libtest/Makefile.inc index 6ee49d0717cf..a9253ecce779 100644 --- a/tests/libtest/Makefile.inc +++ b/tests/libtest/Makefile.inc @@ -334,5 +334,6 @@ TESTS_C = \ lib3208.c \ lib3231.c \ lib3595.c \ + lib3596.c \ lib5000.c \ lib5004.c diff --git a/tests/libtest/lib3596.c b/tests/libtest/lib3596.c new file mode 100644 index 000000000000..7a55859edd36 --- /dev/null +++ b/tests/libtest/lib3596.c @@ -0,0 +1,84 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ +#include "unitcheck.h" + +static CURLcode test_lib3596(const char *arg) +{ + UNITTEST_BEGIN_SIMPLE + +#if !defined(CURL_DISABLE_MIME) && !defined(CURL_DISABLE_HTTP) + + CURL *curl = NULL; + CURLcode result = CURLE_OK; + curl_mime *root = NULL; + curl_mime *current = NULL; + curl_mime *sub = NULL; + curl_mimepart *part = NULL; + int i; + + if(curl_global_init(CURL_GLOBAL_ALL) != CURLE_OK) { + curl_mfprintf(stderr, "curl_global_init() failed\n"); + return TEST_ERR_MAJOR_BAD; + } + + curl = curl_easy_init(); + fail_unless(curl != NULL, "curl_easy_init() failed"); + + /* Create a deeper than MAX_MIME_LEVELS nested mime structure to verify + that header preparation bounds recursion instead of exhausting the + stack. */ + root = curl_mime_init(curl); + fail_unless(root != NULL, "curl_mime_init root failed"); + current = root; + + for(i = 0; i < 50; i++) { + part = curl_mime_addpart(current); + fail_unless(part != NULL, "curl_mime_addpart failed"); + sub = curl_mime_init(curl); + fail_unless(sub != NULL, "curl_mime_init sub failed"); + curl_mime_subparts(part, sub); + current = sub; + } + + result = curl_easy_setopt(curl, CURLOPT_URL, arg); + fail_unless(result == CURLE_OK, "CURLOPT_URL failed"); + if(!result) + result = curl_easy_setopt(curl, CURLOPT_MIMEPOST, root); + fail_unless(result == CURLE_OK, "CURLOPT_MIMEPOST failed"); + + /* Deep nesting must fail gracefully (CURLE_TOO_LARGE), not crash. */ + if(!result) + result = curl_easy_perform(curl); + fail_unless(result == CURLE_TOO_LARGE, + "deeply nested mime did not fail with CURLE_TOO_LARGE"); + + curl_easy_cleanup(curl); + curl_mime_free(root); + curl_global_cleanup(); +#else + (void)arg; +#endif + + UNITTEST_END_SIMPLE +} From 11df32bc8e2d92c50c209a454e6e93b52588b365 Mon Sep 17 00:00:00 2001 From: Paolo Ganci Date: Thu, 17 Sep 2026 17:52:49 +0200 Subject: [PATCH 153/481] http2: don't send a CURLE_SEND_ERROR after a valid http2 response Fixes #22984 Suggested-by: https://github.com/szedenik-adam In this sequence: 1. The client sends part of the request body. 2. The server sends a complete early response and ends its response direction. 3. The client resumes and finishes the request upload. 4. Curl calls `cf_send(len=0, eos=1)` to finalize the request. 5. libcurl sees `stream->closed` and unconditionally returns `CURLE_SEND_ERROR`, even though `stream->resp_hds_complete` is true and the response was valid. change that at point 5 no CURLE_SEND_ERROR is sent if the repsonse is valid Closes #22987 --- lib/http2.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/http2.c b/lib/http2.c index c3604f39d134..be211b085693 100644 --- a/lib/http2.c +++ b/lib/http2.c @@ -2249,7 +2249,7 @@ static CURLcode cf_h2_send(struct Curl_cfilter *cf, struct Curl_easy *data, /* if the stream has been closed in egress handling (nghttp2 does that * when it does not like the headers, for example */ - if(stream && stream->closed) { + if(stream && stream->closed && !stream->resp_hds_complete) { infof(data, "stream %d closed", stream->id); result = CURLE_SEND_ERROR; goto out; From e7f76c782b06ab6af9eb1f256a8d681d7fa13b7c Mon Sep 17 00:00:00 2001 From: Daniel Stenberg Date: Thu, 17 Sep 2026 11:37:52 +0200 Subject: [PATCH 154/481] parsedate: find day names, months and time zones faster Implements faster lookups for which specific week name, month name and time zone that is provided; without loops. With this applied, the dateparser perf test completes almost twice as fast for me. Add unit test 4781 to verify each parser. Closes #22981 --- lib/parsedate.c | 265 +++++++++++++++++++++++++++---------- tests/data/Makefile.am | 1 + tests/data/test4781 | 21 +++ tests/libtest/lib517.c | 1 + tests/unit/Makefile.inc | 3 +- tests/unit/unit4781.c | 283 ++++++++++++++++++++++++++++++++++++++++ 6 files changed, 504 insertions(+), 70 deletions(-) create mode 100644 tests/data/test4781 create mode 100644 tests/unit/unit4781.c diff --git a/lib/parsedate.c b/lib/parsedate.c index ec9f21296b5e..b4dd97bb7f70 100644 --- a/lib/parsedate.c +++ b/lib/parsedate.c @@ -114,115 +114,204 @@ struct tzinfo { #define tDAYZONE (-60) /* offset for daylight savings time */ -/* alpha-sorted list of time zones */ -static const struct tzinfo tz[] = { +/* alpha-sorted list of single-letter time zones */ +static const struct tzinfo tzone[] = { { "A", -1 * 60 }, /* Alpha */ + { "B", -2 * 60 }, /* Bravo */ + { "C", -3 * 60 }, /* Charlie */ + { "D", -4 * 60 }, /* Delta */ + { "E", -5 * 60 }, /* Echo */ + { "F", -6 * 60 }, /* Foxtrot */ + { "G", -7 * 60 }, /* Golf */ + { "H", -8 * 60 }, /* Hotel */ + { "I", -9 * 60 }, /* India */ + { "", -1 }, /* not a timezone */ + { "K", -10 * 60 }, /* Kilo */ + { "L", -11 * 60 }, /* Lima */ + { "M", -12 * 60 }, /* Mike */ + { "N", 60 }, /* November */ + { "O", 2 * 60 }, /* Oscar */ + { "P", 3 * 60 }, /* Papa */ + { "Q", 4 * 60 }, /* Quebec */ + { "R", 5 * 60 }, /* Romeo */ + { "S", 6 * 60 }, /* Sierra */ + { "T", 7 * 60 }, /* Tango */ + { "U", 8 * 60 }, /* Uniform */ + { "V", 9 * 60 }, /* Victor */ + { "W", 10 * 60 }, /* Whiskey */ + { "X", 11 * 60 }, /* X-ray */ + { "Y", 12 * 60 }, /* Yankee */ + { "Z", 0 }, /* Zulu, zero meridian, a.k.a. UTC */ +}; + +/* two-letter time zones */ +static const struct tzinfo tztwo[] = { + { "NT", 660 }, /* Nome */ /* spellchecker:disable-line */ + { "UT", 0 }, /* Universal Time */ +}; + +/* three-letter time zones */ +static const struct tzinfo tzthree[] = { { "ADT", 240 + tDAYZONE }, /* Atlantic Daylight */ - { "AHST", 600 }, /* Alaska-Hawaii Standard */ { "AST", 240 }, /* Atlantic Standard */ - { "B", -2 * 60 }, /* Bravo */ { "BST", 0 + tDAYZONE }, /* British Summer */ - { "C", -3 * 60 }, /* Charlie */ { "CAT", 600 }, /* Central Alaska */ { "CCT", -480 }, /* China Coast, USSR Zone 7 */ { "CDT", 360 + tDAYZONE }, /* Central Daylight */ - { "CEST", -60 + tDAYZONE }, /* Central European Summer */ { "CET", -60 }, /* Central European */ { "CST", 360 }, /* Central Standard */ - { "D", -4 * 60 }, /* Delta */ - { "E", -5 * 60 }, /* Echo */ - { "EADT", -600 + tDAYZONE }, /* Eastern Australian Daylight */ - { "EAST", -600 }, /* Eastern Australian Standard */ { "EDT", 300 + tDAYZONE }, /* Eastern Daylight */ { "EET", -120 }, /* Eastern Europe, USSR Zone 1 */ { "EST", 300 }, /* Eastern Standard */ - { "F", -6 * 60 }, /* Foxtrot */ { "FST", -60 + tDAYZONE }, /* French Summer */ { "FWT", -60 }, /* French Winter */ - { "G", -7 * 60 }, /* Golf */ { "GMT", 0 }, /* Greenwich Mean */ { "GST", -600 }, /* Guam Standard, USSR Zone 9 */ - { "H", -8 * 60 }, /* Hotel */ { "HDT", 600 + tDAYZONE }, /* Hawaii Daylight */ { "HST", 600 }, /* Hawaii Standard */ - { "I", -9 * 60 }, /* India */ - { "IDLE", -720 }, /* International Date Line East */ - { "IDLW", 720 }, /* International Date Line West */ { "JST", -540 }, /* Japan Standard, USSR Zone 8 */ - { "K", -10 * 60 }, /* Kilo */ - { "L", -11 * 60 }, /* Lima */ - { "M", -12 * 60 }, /* Mike */ { "MDT", 420 + tDAYZONE }, /* Mountain Daylight */ - { "MEST", -60 + tDAYZONE }, /* Middle European Summer */ - { "MESZ", -60 + tDAYZONE }, /* Middle European Summer */ { "MET", -60 }, /* Middle European */ - { "MEWT", -60 }, /* Middle European Winter */ { "MST", 420 }, /* Mountain Standard */ - { "N", 60 }, /* November */ - { "NT", 660 }, /* Nome */ /* spellchecker:disable-line */ - { "NZDT", -720 + tDAYZONE }, /* New Zealand Daylight */ - { "NZST", -720 }, /* New Zealand Standard */ { "NZT", -720 }, /* New Zealand */ - { "O", 2 * 60 }, /* Oscar */ - { "P", 3 * 60 }, /* Papa */ { "PDT", 480 + tDAYZONE }, /* Pacific Daylight */ { "PST", 480 }, /* Pacific Standard */ - { "Q", 4 * 60 }, /* Quebec */ - { "R", 5 * 60 }, /* Romeo */ - { "S", 6 * 60 }, /* Sierra */ - { "T", 7 * 60 }, /* Tango */ - { "U", 8 * 60 }, /* Uniform */ - { "UT", 0 }, /* Universal Time */ { "UTC", 0 }, /* Universal (Coordinated) */ - { "V", 9 * 60 }, /* Victor */ - { "W", 10 * 60 }, /* Whiskey */ - { "WADT", -420 + tDAYZONE }, /* West Australian Daylight */ - { "WAST", -420 }, /* spellchecker:disable-line */ - /* West Australian Standard */ { "WAT", 60 }, /* West Africa */ { "WET", 0 }, /* Western European */ - { "X", 11 * 60 }, /* X-ray */ - { "Y", 12 * 60 }, /* Yankee */ { "YDT", 540 + tDAYZONE }, /* Yukon Daylight */ { "YST", 540 }, /* Yukon Standard */ - { "Z", 0 }, /* Zulu, zero meridian, a.k.a. UTC */ }; +/* four-letter time zones */ +static const struct tzinfo tzfoura[] = { + { "AHST", 600 }, /* Alaska-Hawaii Standard */ + { "CEST", -60 + tDAYZONE }, /* Central European Summer */ + { "EADT", -600 + tDAYZONE }, /* Eastern Australian Daylight */ + { "EAST", -600 }, /* Eastern Australian Standard */ + { "IDLE", -720 }, /* International Date Line East */ + { "IDLW", 720 }, /* International Date Line West */ + { "MEST", -60 + tDAYZONE }, /* Middle European Summer */ + { "MESZ", -60 + tDAYZONE }, /* Middle European Summer */ + { "MEWT", -60 }, /* Middle European Winter */ + { "NZDT", -720 + tDAYZONE }, /* New Zealand Daylight */ + { "NZST", -720 }, /* New Zealand Standard */ + { "WADT", -420 + tDAYZONE }, /* West Australian Daylight */ + { "WAST", -420 }, /* spellchecker:disable-line */ + /* West Australian Standard */ +}; + +#define LOWERCASE(x) ((x) | 0x20) + /* returns: -1 no day 0 monday - 6 sunday - */ -static int checkday(const char *check, size_t len) + + @unittest 4781 +*/ +UNITTEST int checkday(const char *check, size_t len); +UNITTEST int checkday(const char *check, size_t len) { - int i; - const char * const *what; - if(len > 3) - what = &weekday[0]; - else if(len == 3) - what = &Curl_wkday[0]; - else + int day = -1; + if(len < 3) return -1; /* too short */ - for(i = 0; i < 7; i++) { - size_t ilen = strlen(what[0]); - if((ilen == len) && - curl_strnequal(check, what[0], len)) - return i; - what++; + + switch(LOWERCASE(check[1])) { + case 'o': /* monday */ + if((LOWERCASE(check[0]) == 'm') && LOWERCASE(check[2]) == 'n') + day = 0; + break; + case 'u': /* tuesday or sunday */ + if((LOWERCASE(check[0]) == 't') && LOWERCASE(check[2]) == 'e') + day = 1; + else if((LOWERCASE(check[0]) == 's') && LOWERCASE(check[2]) == 'n') + day = 6; + break; + case 'e': /* wednesday */ + if((LOWERCASE(check[0]) == 'w') && LOWERCASE(check[2]) == 'd') + day = 2; + break; + case 'h': /* thursday */ + if((LOWERCASE(check[0]) == 't') && LOWERCASE(check[2]) == 'u') + day = 3; + break; + case 'r': /* friday */ + if((LOWERCASE(check[0]) == 'f') && LOWERCASE(check[2]) == 'i') + day = 4; + break; + case 'a': /* saturday */ + if((LOWERCASE(check[0]) == 's') && LOWERCASE(check[2]) == 't') + day = 5; + break; } - return -1; + if((len > 3) && (day != -1)) { + /* when more than three letters are provided, verify the full name case + insensitively */ + size_t wlen = strlen(weekday[day]); + if((len != wlen) || !curl_strnequal(&check[3], &weekday[day][3], len - 3)) + return -1; + } + return day; } -static int checkmonth(const char *check, size_t len) +/* @unittest 4781 */ + +UNITTEST int checkmonth(const char *check, size_t len); +UNITTEST int checkmonth(const char *check, size_t len) { - int i; - const char * const *what = &Curl_month[0]; if(len != 3) return -1; /* not a month */ - for(i = 0; i < 12; i++) { - if(curl_strnequal(check, what[0], 3)) - return i; - what++; + switch(LOWERCASE(check[2])) { + case 'n': /* jan, jun */ + if(LOWERCASE(check[0]) == 'j') { + uint8_t c2 = LOWERCASE(check[1]); + if(c2 == 'a') + return 0; + else if(c2 == 'u') + return 5; + } + break; + case 'b': /* feb */ + if((LOWERCASE(check[0]) == 'f') && LOWERCASE(check[1]) == 'e') + return 1; + break; + case 'r': /* mar, apr */ + if(LOWERCASE(check[0]) == 'm') { + if(LOWERCASE(check[1]) == 'a') + return 2; + } + else if((LOWERCASE(check[0]) == 'a') && LOWERCASE(check[1]) == 'p') + return 3; + break; + case 'y': /* may */ + if((LOWERCASE(check[0]) == 'm') && LOWERCASE(check[1]) == 'a') + return 4; + break; + case 'l': /* jul */ + if((LOWERCASE(check[0]) == 'j') && LOWERCASE(check[1]) == 'u') + return 6; + break; + case 'g': /* aug */ + if((LOWERCASE(check[0]) == 'a') && LOWERCASE(check[1]) == 'u') + return 7; + break; + case 'p': /* sep */ + if((LOWERCASE(check[0]) == 's') && LOWERCASE(check[1]) == 'e') + return 8; + break; + case 't': /* oct */ + if((LOWERCASE(check[0]) == 'o') && LOWERCASE(check[1]) == 'c') + return 9; + break; + case 'v': /* nov */ + if((LOWERCASE(check[0]) == 'n') && LOWERCASE(check[1]) == 'o') + return 10; + break; + case 'c': /* dec */ + if((LOWERCASE(check[0]) == 'd') && LOWERCASE(check[1]) == 'e') + return 11; + break; } return -1; /* return the offset or -1, no real offset is -1 */ } @@ -235,14 +324,52 @@ static int tzcompare(const void *m1, const void *m2) } /* return the time zone offset between GMT and the input one, in number of - seconds or -1 if the timezone was not found/legal */ -static int checktz(const char *check, size_t len) + seconds or -1 if the timezone was not found/legal + + @unittest 4781 +*/ +UNITTEST int checktz(const char *check, size_t len); +UNITTEST int checktz(const char *check, size_t len) { - if(len <= 4) { + if(len > 4) + return -1; + else if(len == 1) { + /* short-cut single letter names */ + if((check[0] < 'A') || (check[0] > 'Z') || (check[0] == 'J')) + return -1; /* no such tz */ + return tzone[check[0] - 'A'].offset * 60; + } + else if(len == 2) { + /* two-letter names */ + if(check[1] == 'T') { + if(check[0] == 'N') + return tztwo[0].offset * 60; + else if(check[0] == 'U') + return tztwo[1].offset * 60; + } + return -1; /* nope */ + } + else if(len == 3) { + /* three-letter name */ + const struct tzinfo *what; + struct tzinfo find; + if((check[0] < 'A') || (check[0] > 'Y')) + return -1; + curlx_strcopy(find.name, sizeof(find.name), check, len); + what = bsearch(&find, tzthree, CURL_ARRAYSIZE(tzthree), + sizeof(tzthree[0]), tzcompare); + if(what) + return what->offset * 60; + } + else { + /* four-letter name */ const struct tzinfo *what; struct tzinfo find; + if((check[0] < 'A') || (check[0] > 'W')) + return -1; curlx_strcopy(find.name, sizeof(find.name), check, len); - what = bsearch(&find, tz, CURL_ARRAYSIZE(tz), sizeof(tz[0]), tzcompare); + what = bsearch(&find, tzfoura, CURL_ARRAYSIZE(tzfoura), + sizeof(tzfoura[0]), tzcompare); if(what) return what->offset * 60; } diff --git a/tests/data/Makefile.am b/tests/data/Makefile.am index f5eef86be69d..2e43b7d7f92a 100644 --- a/tests/data/Makefile.am +++ b/tests/data/Makefile.am @@ -2116,6 +2116,7 @@ TESTCASES = \ test4644 \ test4656 \ test4657 \ + test4781 \ test5000 \ test5001 \ test5002 \ diff --git a/tests/data/test4781 b/tests/data/test4781 new file mode 100644 index 000000000000..556054dbf663 --- /dev/null +++ b/tests/data/test4781 @@ -0,0 +1,21 @@ + + + + +unittest +weekday +day +monthname + + + + + +unittest + + +weekday, month name and timezone parsers + + + + diff --git a/tests/libtest/lib517.c b/tests/libtest/lib517.c index 609a44a06b2b..4df2b0c3f937 100644 --- a/tests/libtest/lib517.c +++ b/tests/libtest/lib517.c @@ -31,6 +31,7 @@ static CURLcode test_lib517(const char *URL) }; static const struct dcheck dates[] = { + { "Sat May 5 GMT 11:56:27 2035", 2061978987 }, { "Sun, 06 Nov 1994 08:49:37 GMT", 784111777 }, { "Sunday, 06-Nov-94 08:49:37 GMT", 784111777 }, { "Sun Nov 6 08:49:37 1994", 784111777 }, diff --git a/tests/unit/Makefile.inc b/tests/unit/Makefile.inc index 0f51a3bc802c..b152bc029ec0 100644 --- a/tests/unit/Makefile.inc +++ b/tests/unit/Makefile.inc @@ -108,4 +108,5 @@ TESTS_C = \ unit3303.c \ unit3304.c \ unit3306.c \ - unit3400.c + unit3400.c \ + unit4781.c diff --git a/tests/unit/unit4781.c b/tests/unit/unit4781.c new file mode 100644 index 000000000000..962352bb3e63 --- /dev/null +++ b/tests/unit/unit4781.c @@ -0,0 +1,283 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ +#include "unitcheck.h" + +static CURLcode test_unit4781(const char *arg) +{ +#ifndef CURL_DISABLE_PARSEDATE + unsigned int i; + int errors = 0; + struct day { + const char *name; + int val; + }; + + /* three-letter version or full version, nothing else */ + static const struct day days[] = { + { "wedmesday", -1 }, + { "monray", -1 }, + { "mon", 0 }, + { "tue", 1 }, + { "wed", 2 }, + { "thu", 3 }, + { "fri", 4 }, + { "sat", 5 }, + { "sun", 6 }, + { "monday", 0 }, + { "tuesday", 1 }, + { "wednesday", 2 }, + { "thursday", 3 }, + { "friday", 4 }, + { "saturday", 5 }, + { "sunday", 6 }, + { "monDAY", 0 }, + { "tueSDAY", 1 }, + { "wedneSDAY", 2 }, + { "thursdAY", 3 }, + { "frIDAY", 4 }, + { "satURDAY", 5 }, + { "suNDAY", 6 }, + { "monda", -1 }, + { "tuesda", -1 }, + { "wednesda", -1 }, + { "thursda", -1 }, + { "frida", -1 }, + { "saturda", -1 }, + { "sunda", -1 }, + { "MON", 0 }, + { "TUE", 1 }, + { "WED", 2 }, + { "THU", 3 }, + { "FRI", 4 }, + { "SAT", 5 }, + { "SUN", 6 }, + { "mo", -1 }, + { "tu", -1 }, + { "we", -1 }, + { "th", -1 }, + { "fr", -1 }, + { "sa", -1 }, + { "su", -1 }, + { "mom", -1 }, + { "tuf", -1 }, + { "wef", -1 }, + { "thv", -1 }, + { "frj", -1 }, + { "sau", -1 }, + { "sum", -1 }, + { "abc", -1 }, + { " monday", -1 }, + { " tuesday", -1 }, + { " wednesday", -1 }, + { " thursday", -1 }, + { " friday", -1 }, + { " saturday", -1 }, + { " sunday", -1 }, + { "monday ", -1 }, + { "tuesday ", -1 }, + { "wednesday ", -1 }, + { "thursday ", -1 }, + { "friday ", -1 }, + { "saturday ", -1 }, + { "sunday ", -1 }, + }; + + /* three-letter month versions only */ + static const struct day months[] = { + { "monday", -1 }, + { "tuesday", -1 }, + { "wednesday", -1 }, + { "thursday", -1 }, + { "friday", -1 }, + { "saturday", -1 }, + { "jan", 0 }, + { "feb", 1 }, + { "mar", 2 }, + { "apr", 3 }, + { "may", 4 }, + { "jun", 5 }, + { "jul", 6 }, + { "aug", 7 }, + { "sep", 8 }, + { "oct", 9 }, + { "nov", 10 }, + { "dec", 11 }, + { "january", -1 }, + { "february", -1 }, + { "march", -1 }, + { "april", -1 }, + { "june", -1 }, + { "july", -1 }, + { "august", -1 }, + { "september", -1 }, + { "october", -1 }, + { "november", -1 }, + { "december", -1 }, + { "janu", -1 }, + { "febr", -1 }, + { "marc", -1 }, + { "apri", -1 }, + { "june", -1 }, + { "july", -1 }, + { "augu", -1 }, + { "sept", -1 }, + { "octo", -1 }, + { "nove", -1 }, + { "dece", -1 }, + { "JAN", 0 }, + { "FEB", 1 }, + { "MAR", 2 }, + { "APR", 3 }, + { "MAY", 4 }, + { "JUN", 5 }, + { "JUL", 6 }, + { "AUG", 7 }, + { "SEP", 8 }, + { "OCT", 9 }, + { "NOV", 10 }, + { "DEC", 11 }, + }; + + static const struct day timezones[] = { + { "A", -3600 }, + { "B", -7200 }, + { "a", -1 }, + { "b", -1 }, + { "C", -10800 }, + { "D", -14400 }, + { "E", -18000 }, + { "F", -21600 }, + { "G", -25200 }, + { "H", -28800 }, + { "I", -32400 }, + { "J", -1 }, + { "K", -36000 }, + { "L", -39600 }, + { "M", -43200 }, + { "N", 3600 }, + { "O", 7200 }, + { "P", 10800 }, + { "Q", 14400 }, + { "R", 18000 }, + { "S", 21600 }, + { "T", 25200 }, + { "U", 28800 }, + { "V", 32400 }, + { "X", 39600 }, + { "Y", 43200 }, + { "Z", 0 }, + { "z", -1 }, + { "@", -1 }, + + { "NT", 39600 }, + { "UT", 0 }, + { "nt", -1 }, + { "ut", -1 }, + { "PT", -1 }, + { "CT", -1 }, + + { "ADT", 10800 }, + { "AST", 14400 }, + { "BST", -3600 }, + { "CAT", 36000 }, + { "CCT", -28800 }, + { "CDT", 18000 }, + { "CET", -3600 }, + { "CST", 21600 }, + { "EDT", 14400 }, + { "EET", -7200 }, + { "EST", 18000 }, + { "FST", -7200 }, + { "FWT", -3600 }, + { "GMT", 0 }, + { "GST", -36000 }, + { "HDT", 32400 }, + { "HST", 36000 }, + { "JST", -32400 }, + { "MDT", 21600 }, + { "MET", -3600 }, + { "MST", 25200 }, + { "NZT", -43200 }, + { "PDT", 25200 }, + { "PST", 28800 }, + { "UTC", 0 }, + { "WAT", 3600 }, + { "WET", 0 }, + { "YDT", 28800 }, + { "YST", 32400 }, + { "yST", -1 }, + { "ZST", -1 }, + + { "AHST", 36000 }, + { "BHST", -1 }, + { "ahst", -1 }, + { "CEST", -7200 }, + { "cest", -1 }, + { "EADT", -39600 }, + { "EAST", -36000 }, + { "IDLE", -43200 }, + { "IDLW", 43200 }, + { "MEST", -7200 }, + { "MESZ", -7200 }, + { "MEWT", -3600 }, + { "NZDT", -46800 }, + { "NZST", -43200 }, + { "WADT", -28800 }, + { "WAST", -25200 } /* spellchecker:disable-line */ + }; + + (void)arg; + + for(i = 0; i < CURL_ARRAYSIZE(days); i++) { + int d = checkday(days[i].name, strlen(days[i].name)); + if(d != days[i].val) { + curl_mfprintf(stderr, "Day: %s returned %d, expected %d\n", + days[i].name, d, days[i].val); + errors++; + } + } + for(i = 0; i < CURL_ARRAYSIZE(months); i++) { + int d = checkmonth(months[i].name, strlen(months[i].name)); + if(d != months[i].val) { + curl_mfprintf(stderr, "Month: %s returned %d, expected %d\n", + months[i].name, d, months[i].val); + errors++; + } + } + for(i = 0; i < CURL_ARRAYSIZE(timezones); i++) { + int d = checktz(timezones[i].name, strlen(timezones[i].name)); + if(d != timezones[i].val) { + curl_mfprintf(stderr, "TZ: %s returned %d, expected %d\n", + timezones[i].name, d, timezones[i].val); + errors++; + } + } + if(errors) + return CURLE_BAD_FUNCTION_ARGUMENT; +#else /* CURL_DISABLE_PARSEDATE */ + /* nothing to do */ + (void)arg; +#endif + return CURLE_OK; +} From ccecc619a5f2dbfddca92d9e61ff32d0e1a39366 Mon Sep 17 00:00:00 2001 From: xhon-pelushi Date: Mon, 14 Sep 2026 11:42:49 -0400 Subject: [PATCH 155/481] KNOWN_BUGS: drop the empty error buffer entry, it no longer happens Issue 544 asked for a failf() in the connect path and was closed in April 2016. The call is there, but the entry stayed. Checked against current master by reading CURLOPT_ERRORBUFFER from a program rather than going by what the curl tool prints, and driving the failures with CURLOPT_RESOLVE. The buffer is set every time, with one, two, three and four addresses, for IPv4 and IPv6 alike. The entry describes addresses that cannot be reached at all rather than ones that time out, so the same set was run again inside a network namespace holding only loopback and no routes, where connect() fails immediately. That returns CURLE_COULDNT_CONNECT, which is the case the entry names, and the buffer still holds "Failed to connect to after 0 ms: Could not connect to server". Fixes #544 Closes #22952 --- docs/KNOWN_BUGS.md | 7 ------- 1 file changed, 7 deletions(-) diff --git a/docs/KNOWN_BUGS.md b/docs/KNOWN_BUGS.md index 58e816cfc975..90dcdd9c48ec 100644 --- a/docs/KNOWN_BUGS.md +++ b/docs/KNOWN_BUGS.md @@ -347,13 +347,6 @@ The struct needs to be expanded and code added to store this info. See [curl issue 13492](https://github.com/curl/curl/issues/13492) -## error buffer not set if connection to multiple addresses fails - -If you ask libcurl to resolve a hostname like example.com to IPv6 addresses -when you only have IPv4 connectivity. libcurl fails with -`CURLE_COULDNT_CONNECT`, but the error buffer set by `CURLOPT_ERRORBUFFER` -remains empty. Issue: [curl issue 544](https://github.com/curl/curl/issues/544) - ## HTTP test server 'connection-monitor' problems The `connection-monitor` feature of the HTTP test server does not work From cba4a7580e33d83884057bb67a0ea7bb6ba7d275 Mon Sep 17 00:00:00 2001 From: Joshua Rogers Date: Sat, 12 Sep 2026 15:50:46 +0200 Subject: [PATCH 156/481] tool_help: guard category[2] access when category is bare "-" When category is "-" (length 1), category[1] is already the null terminator. The else-if on line 272 then reads category[2] without first confirming category[1] is non-null, producing a one-byte heap-buffer-overflow that ASan flags. Add the category[1] guard so the short-option path is only taken for exactly two-character inputs like "-x". Closes #22957 --- src/tool_help.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tool_help.c b/src/tool_help.c index 597a6a554d3f..aaf69282c10f 100644 --- a/src/tool_help.c +++ b/src/tool_help.c @@ -269,7 +269,7 @@ void tool_help(const char *category) option */ a = NULL; } - else if(!category[2]) + else if(category[1] && !category[2]) a = findshortopt(category[1]); if(!a) { curl_mfprintf(tool_stderr, "Incorrect option name to show help for," From 228f3a5faccc8cd6e8dfb571b3cf7fa2691a204e Mon Sep 17 00:00:00 2001 From: Bobbeh Rhino Date: Mon, 14 Sep 2026 06:44:00 -0400 Subject: [PATCH 157/481] ldap: base64-encode a non-SAFE DN and reject a control-byte type name The LDIF output already base64-encodes attribute values that are not RFC 2849 SAFE-STRINGs, but the entry DN and the attribute type names were still written verbatim. A hostile or MITM LDAP server that returns a DN or a type name containing CR/LF could therefore inject its own lines into the LDIF that curl prints. Follow RFC 2849 for those two fields as well: write a non-SAFE DN in the "DN:: " form, and reject a type name that carries a control byte (a type has no base64 form in LDIF and a conformant one never contains one). The SAFE-STRING test is factored into one helper per backend and the existing value check now shares it. Applies to both the OpenLDAP (lib/openldap.c) and the generic/WinLDAP (lib/ldap.c) backends. Closes #22945 --- lib/ldap.c | 67 +++++++++++++++++++++++++++++++++++--------------- lib/openldap.c | 65 +++++++++++++++++++++++++++++++++--------------- 2 files changed, 92 insertions(+), 40 deletions(-) diff --git a/lib/ldap.c b/lib/ldap.c index 8355560ecfc4..70422f217b47 100644 --- a/lib/ldap.c +++ b/lib/ldap.c @@ -230,27 +230,30 @@ static ULONG ldap_win_bind(struct Curl_easy *data, LDAP *server, } #endif /* USE_WIN32_LDAP */ +/* RFC 2849: a string that is not a SAFE-STRING (leading ':' or '<', leading or + trailing space, or any non-printable byte such as CR/LF) cannot go into LDIF + as-is. DNs and values get base64-encoded instead; an attribute type has no + base64 form in LDIF and is rejected by the caller. */ +static bool ldap_str_needs_base64(const char *s, size_t len) +{ + size_t i; + if(!len) + return FALSE; + if((s[0] == ':') || (s[0] == '<') || ISBLANK(s[0]) || ISBLANK(s[len - 1])) + return TRUE; + for(i = 0; i < len; i++) + if(!ISPRINT(s[i])) + return TRUE; + return FALSE; +} + static bool ldap_value_needs_base64(const char *attr, size_t attr_len, const BerValue *val) { - ber_len_t j; - if((attr_len > 7) && curl_strequal(";binary", attr + attr_len - 7)) return TRUE; - /* check for a leading ':' or '<' (not a SAFE-INIT-CHAR per RFC 2849) or - leading or trailing whitespace */ - if(val->bv_len && ((val->bv_val[0] == ':') || (val->bv_val[0] == '<') || - ISBLANK(val->bv_val[0]) || - ISBLANK(val->bv_val[val->bv_len - 1]))) - return TRUE; - - /* check for unprintable characters */ - for(j = 0; j < val->bv_len; j++) - if(!ISPRINT(val->bv_val[j])) - return TRUE; - - return FALSE; + return ldap_str_needs_base64(val->bv_val, val->bv_len); } #ifdef USE_WIN32_LDAP @@ -271,6 +274,14 @@ static CURLcode show_vals(struct Curl_easy *data, BerValue **vals, int i; size_t attr_len = strlen(attr); + /* An attribute type has no base64 form in LDIF, so a type carrying control + bytes (CR/LF etc.) would let a hostile server inject lines. A conformant + type name never contains them, so reject it. */ + if(ldap_str_needs_base64(attr, attr_len)) { + failf(data, "LDAP local: attribute type contains illegal bytes"); + return CURLE_WEIRD_SERVER_REPLY; + } + for(i = 0; vals[i] && !result; i++) { result = Curl_client_write(data, CLIENTWRITE_BODY, "\t", 1); if(!result) @@ -534,12 +545,28 @@ static CURLcode ldap_do(struct Curl_easy *data, bool *done) result = dn ? CURLE_OUT_OF_MEMORY : CURLE_FAILED_INIT; else { name_len = strlen(name); - result = Curl_client_write(data, CLIENTWRITE_BODY, "DN: ", 4); + if(ldap_str_needs_base64(name, name_len)) { + char *dn_b64 = NULL; + size_t dn_b64_sz = 0; + + /* A DN with e.g. CR/LF would inject LDIF lines, so encode it */ + result = curlx_base64_encode((uint8_t *)name, name_len, + &dn_b64, &dn_b64_sz); + if(!result) + result = Curl_client_write(data, CLIENTWRITE_BODY, "DN:: ", 5); + if(!result) + result = Curl_client_write(data, CLIENTWRITE_BODY, dn_b64, + dn_b64_sz); + curlx_free(dn_b64); + } + else { + result = Curl_client_write(data, CLIENTWRITE_BODY, "DN: ", 4); + if(!result) + result = Curl_client_write(data, CLIENTWRITE_BODY, name, name_len); + } + if(!result) + result = Curl_client_write(data, CLIENTWRITE_BODY, "\n", 1); } - if(!result) - result = Curl_client_write(data, CLIENTWRITE_BODY, name, name_len); - if(!result) - result = Curl_client_write(data, CLIENTWRITE_BODY, "\n", 1); FREE_ON_WINLDAP(name); ldap_memfree(dn); if(result) diff --git a/lib/openldap.c b/lib/openldap.c index 03f9c2f1fa9e..8f2651294a79 100644 --- a/lib/openldap.c +++ b/lib/openldap.c @@ -1100,6 +1100,23 @@ static CURLcode client_write(struct Curl_easy *data, return result; } +/* RFC 2849: a string that is not a SAFE-STRING (leading ':' or '<', leading or + trailing space, or any non-printable byte such as CR/LF) cannot go into LDIF + as-is. DNs and values get base64-encoded instead; an attribute type has no + base64 form in LDIF and is rejected by the caller. */ +static bool oldap_str_needs_base64(const char *s, size_t len) +{ + size_t i; + if(!len) + return FALSE; + if((s[0] == ':') || (s[0] == '<') || ISBLANK(s[0]) || ISBLANK(s[len - 1])) + return TRUE; + for(i = 0; i < len; i++) + if(!ISPRINT(s[i])) + return TRUE; + return FALSE; +} + static CURLcode oldap_recv(struct Curl_easy *data, int8_t sockindex, char *buf, size_t len, size_t *pnread) { @@ -1172,8 +1189,21 @@ static CURLcode oldap_recv(struct Curl_easy *data, int8_t sockindex, char *buf, break; } - result = client_write(data, STRCONST("DN: "), bv.bv_val, bv.bv_len, - STRCONST("\n")); + if(oldap_str_needs_base64(bv.bv_val, bv.bv_len)) { + char *dn_b64 = NULL; + size_t dn_b64_sz = 0; + + /* A DN with e.g. CR/LF would inject LDIF lines, so base64-encode it. */ + result = curlx_base64_encode((uint8_t *)bv.bv_val, bv.bv_len, + &dn_b64, &dn_b64_sz); + if(!result) + result = client_write(data, STRCONST("DN:: "), dn_b64, dn_b64_sz, + STRCONST("\n")); + curlx_free(dn_b64); + } + else + result = client_write(data, STRCONST("DN: "), bv.bv_val, bv.bv_len, + STRCONST("\n")); if(result) break; @@ -1186,6 +1216,17 @@ static CURLcode oldap_recv(struct Curl_easy *data, int8_t sockindex, char *buf, if(!bv.bv_val) break; + /* An attribute type has no base64 form in LDIF, so a type carrying + control bytes (CR/LF etc.) would let a hostile server inject lines. + A conformant type name never contains them, so reject it. */ + if(oldap_str_needs_base64(bv.bv_val, bv.bv_len)) { + failf(data, "LDAP local: attribute type contains illegal bytes"); + if(bvals) + ber_memfree(bvals); + result = CURLE_WEIRD_SERVER_REPLY; + break; + } + if(!bvals) { result = client_write(data, STRCONST("\t"), bv.bv_val, bv.bv_len, STRCONST(":\n")); @@ -1205,24 +1246,8 @@ static CURLcode oldap_recv(struct Curl_easy *data, int8_t sockindex, char *buf, if(result) break; - if(!binary) { - /* check for a leading ':' or '<' (not a SAFE-INIT-CHAR per RFC - 2849) or leading or trailing whitespace */ - if(bvals[i].bv_len && - ((bvals[i].bv_val[0] == ':') || (bvals[i].bv_val[0] == '<') || - ISBLANK(bvals[i].bv_val[0]) || - ISBLANK(bvals[i].bv_val[bvals[i].bv_len - 1]))) - binval = TRUE; - else { - /* check for unprintable characters */ - unsigned int j; - for(j = 0; j < bvals[i].bv_len; j++) - if(!ISPRINT(bvals[i].bv_val[j])) { - binval = TRUE; - break; - } - } - } + if(!binary) + binval = oldap_str_needs_base64(bvals[i].bv_val, bvals[i].bv_len); if(binary || binval) { char *val_b64 = NULL; size_t val_b64_sz = 0; From b5c334937127a2743889519ceeb558f9e214b600 Mon Sep 17 00:00:00 2001 From: Joshua Rogers Date: Mon, 14 Sep 2026 10:47:45 +0200 Subject: [PATCH 158/481] rtsp: do not send a learned session id to another origin A Session: id issued by one server was forwarded across a redirect to the next origin. Remember the origin that issued it and suppress the header elsewhere, unless CURLOPT_UNRESTRICTED_AUTH is set. An id set with CURLOPT_RTSP_SESSION_ID is unaffected. Closes #22920 --- docs/libcurl/opts/CURLOPT_RTSP_SESSION_ID.md | 3 +- lib/rtsp.c | 8 +++ lib/setopt.c | 1 + lib/url.c | 3 + lib/urldata.h | 2 + tests/data/Makefile.am | 1 + tests/data/test3233 | 59 +++++++++++++++++ tests/libtest/Makefile.inc | 1 + tests/libtest/lib3233.c | 70 ++++++++++++++++++++ 9 files changed, 147 insertions(+), 1 deletion(-) create mode 100644 tests/data/test3233 create mode 100644 tests/libtest/lib3233.c diff --git a/docs/libcurl/opts/CURLOPT_RTSP_SESSION_ID.md b/docs/libcurl/opts/CURLOPT_RTSP_SESSION_ID.md index 061e8228f73d..5c12cc5c840b 100644 --- a/docs/libcurl/opts/CURLOPT_RTSP_SESSION_ID.md +++ b/docs/libcurl/opts/CURLOPT_RTSP_SESSION_ID.md @@ -31,7 +31,8 @@ Session ID for the handle. Useful for resuming an in-progress session. Once this value is set to any non-NULL value, libcurl returns *CURLE_RTSP_SESSION_ERROR* if the ID received from the server does not match. If unset (or set to NULL), libcurl automatically sets the ID the first time -the server sets it in a response. +the server sets it in a response. Such an ID is only sent back to the origin +that issued it, unless CURLOPT_UNRESTRICTED_AUTH(3) is set. The application does not have to keep the string around after setting this option. diff --git a/lib/rtsp.c b/lib/rtsp.c index 80ed636147dd..72b57c0ca5ef 100644 --- a/lib/rtsp.c +++ b/lib/rtsp.c @@ -41,6 +41,7 @@ #include "curlx/strdup.h" #include "bufref.h" #include "curlx/strparse.h" +#include "peer.h" /* meta key for storing protocol meta at easy handle */ #define CURL_META_RTSP_EASY "meta:proto:rtsp:easy" @@ -457,6 +458,12 @@ static CURLcode rtsp_do(struct Curl_easy *data, bool *done) result = rtsp_setup_request(data, &block, rtspreq); if(result) goto out; + + if(block.session_id && data->state.rtsp_session_origin && + !data->set.allow_auth_to_other_hosts && + !Curl_peer_equal(data->state.origin, data->state.rtsp_session_origin)) + block.session_id = NULL; + /* * Sanity check the custom headers */ @@ -1014,6 +1021,7 @@ CURLcode Curl_rtsp_parseheader(struct Curl_easy *data, const char *header) if(!mem || CURL_EASY_STR_SETN(data, STRING_RTSP_SESSION_ID, mem)) return CURLE_OUT_OF_MEMORY; + Curl_peer_link(&data->state.rtsp_session_origin, data->state.origin); } } else if(checkprefix("Transport:", header)) { diff --git a/lib/setopt.c b/lib/setopt.c index 1dca23731c65..5e52a6dae676 100644 --- a/lib/setopt.c +++ b/lib/setopt.c @@ -2417,6 +2417,7 @@ static CURLcode setopt_cptr_misc(struct Curl_easy *data, CURLoption option, return Curl_setstropt(data, STRING_SASL_AUTHZID, ptr); #ifndef CURL_DISABLE_RTSP case CURLOPT_RTSP_SESSION_ID: + Curl_peer_unlink(&data->state.rtsp_session_origin); return Curl_setstropt(data, STRING_RTSP_SESSION_ID, ptr); case CURLOPT_RTSP_STREAM_URI: return Curl_setstropt(data, STRING_RTSP_STREAM_URI, ptr); diff --git a/lib/url.c b/lib/url.c index 47345eb5f85b..99657a657ee4 100644 --- a/lib/url.c +++ b/lib/url.c @@ -244,6 +244,9 @@ CURLcode Curl_close(struct Curl_easy **datap) Curl_ssl_close_all(data); Curl_peer_unlink(&data->state.origin); Curl_peer_unlink(&data->state.initial_origin); +#ifndef CURL_DISABLE_RTSP + Curl_peer_unlink(&data->state.rtsp_session_origin); +#endif Curl_ssl_free_certinfo(data); Curl_bufref_free(&data->state.referer); diff --git a/lib/urldata.h b/lib/urldata.h index fe2e3f2c36bf..1ff2cbb2a98e 100644 --- a/lib/urldata.h +++ b/lib/urldata.h @@ -620,6 +620,8 @@ struct UrlState { #endif #ifndef CURL_DISABLE_RTSP /* This RTSP state information survives requests and connections */ + struct Curl_peer *rtsp_session_origin; /* origin that issued the session id, + NULL when set by the application */ uint8_t rtp_channel_mask[32]; /* for the correctness checking of the interleaved data */ uint32_t rtsp_next_client_CSeq; /* the session's next client CSeq */ diff --git a/tests/data/Makefile.am b/tests/data/Makefile.am index 2e43b7d7f92a..f743e0e77a18 100644 --- a/tests/data/Makefile.am +++ b/tests/data/Makefile.am @@ -2096,6 +2096,7 @@ TESTCASES = \ test3230 \ test3231 \ test3232 \ + test3233 \ \ test3300 \ test3301 \ diff --git a/tests/data/test3233 b/tests/data/test3233 new file mode 100644 index 000000000000..747ccbd9d678 --- /dev/null +++ b/tests/data/test3233 @@ -0,0 +1,59 @@ + + + + +RTSP +SESSION_ID +followlocation + + + +# Server-side + + +RTSP/1.0 302 Found +Server: RTSPD/libcurl-test +CSeq: 1 +Session: SHOULD-NOT-LEAK +Location: rtsp://example.com:%RTSPPORT/%TESTNUMBER +Curl-private: swsbounce swsclose + + + +RTSP/1.0 200 OK +Server: RTSPD/libcurl-test +CSeq: 2 +Curl-private: swsclose + + + + +# Client-Side + + +rtsp + + +lib%TESTNUMBER + + + +RTSP session id is not sent to another origin after a redirect + + +rtsp://%HOSTIP:%RTSPPORT/%TESTNUMBER0001 example.com:%RTSPPORT:%HOSTIP + + + + + +OPTIONS rtsp://%HOSTIP:%RTSPPORT/%TESTNUMBER0001 RTSP/1.0 +CSeq: 1 + +OPTIONS rtsp://%HOSTIP:%RTSPPORT/%TESTNUMBER0001 RTSP/1.0 +CSeq: 2 + + + + + diff --git a/tests/libtest/Makefile.inc b/tests/libtest/Makefile.inc index a9253ecce779..270e6b4d465e 100644 --- a/tests/libtest/Makefile.inc +++ b/tests/libtest/Makefile.inc @@ -333,6 +333,7 @@ TESTS_C = \ lib3207.c \ lib3208.c \ lib3231.c \ + lib3233.c \ lib3595.c \ lib3596.c \ lib5000.c \ diff --git a/tests/libtest/lib3233.c b/tests/libtest/lib3233.c new file mode 100644 index 000000000000..b411e60f5388 --- /dev/null +++ b/tests/libtest/lib3233.c @@ -0,0 +1,70 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ +#include "first.h" + +/* + * A session id learned from one origin must not be sent to another one + * after a redirect. + */ +static CURLcode test_lib3233(const char *URL) +{ + CURLcode result; + CURL *curl; + struct curl_slist *resolve = NULL; + + if(curl_global_init(CURL_GLOBAL_ALL) != CURLE_OK) { + curl_mfprintf(stderr, "curl_global_init() failed\n"); + return TEST_ERR_MAJOR_BAD; + } + + curl = curl_easy_init(); + if(!curl) { + curl_mfprintf(stderr, "curl_easy_init() failed\n"); + curl_global_cleanup(); + return TEST_ERR_MAJOR_BAD; + } + + resolve = curl_slist_append(resolve, libtest_arg2); + + easy_setopt(curl, CURLOPT_HEADERDATA, stdout); + easy_setopt(curl, CURLOPT_WRITEDATA, stdout); + easy_setopt(curl, CURLOPT_VERBOSE, 1L); + + easy_setopt(curl, CURLOPT_URL, URL); + easy_setopt(curl, CURLOPT_RTSP_STREAM_URI, URL); + easy_setopt(curl, CURLOPT_RTSP_REQUEST, CURL_RTSPREQ_OPTIONS); + easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); + easy_setopt(curl, CURLOPT_REDIR_PROTOCOLS_STR, "rtsp"); + easy_setopt(curl, CURLOPT_RESOLVE, resolve); + + result = curl_easy_perform(curl); + +test_cleanup: + + curl_slist_free_all(resolve); + curl_easy_cleanup(curl); + curl_global_cleanup(); + + return result; +} From 22d135e29557ab81c0e6d5a4edf6732bf5fef991 Mon Sep 17 00:00:00 2001 From: Daniel Stenberg Date: Thu, 17 Sep 2026 22:39:12 +0200 Subject: [PATCH 159/481] RELEASE-NOTES: synced Make THANKS-filter remove https:// URLs --- RELEASE-NOTES | 67 +++++++++++++++++++++++++++++++++++++++------- docs/THANKS-filter | 1 + 2 files changed, 59 insertions(+), 9 deletions(-) diff --git a/RELEASE-NOTES b/RELEASE-NOTES index dd043fcff817..7dce782a2604 100644 --- a/RELEASE-NOTES +++ b/RELEASE-NOTES @@ -4,8 +4,8 @@ curl and libcurl 8.22.1 Command line options: 278 curl_easy_setopt() options: 312 Public functions in libcurl: 100 - Authors: 1529 - Contributors: 3804 + Authors: 1532 + Contributors: 3812 This release includes the following changes: @@ -24,6 +24,7 @@ This release includes the following bugfixes: o conncache: avoid evicting connect-only connections [79] o conncache: remove bundle dest [50] o connectdata: use less curltime [34] + o cookie: accept slightly longer lines in cookie jars [105] o cookie: ignore individual cookie errors when loading [6] o cshutdown: make it smaller [60] o curl_addrinfo: drop redundant parentheses [41] @@ -46,28 +47,41 @@ This release includes the following bugfixes: o fnmatch: replace recursive matcher with iterative greedy algorithm [72] o ftp: tighten use_ssl check for conn reuse [19] o ftp: URL encode file names when wildcarding [61] + o ftplistparser: no more overwriting set.write_func [100] o getparam: switch off CONNECT headers when using -J [4] + o glob: reduce the number of allocations for URLs that don't need globbing. [109] o global_init: tidy up, simplify, fix issues [43] o happy eyeballs: bound work from many duplicate/failing addresses [80] o hash: change key arguments to const [14] + o hash: vary bucket placement across process runs [108] o headers: avoid O(n^2) rescans in curl_easy_nextheader() [81] o hsts: cap max-age to two years [47] o hsts: on load, only remove exact duplicates [56] o HTTP-CONNECT: do not react to 401 responses [31] o http2: changes in error handling [53] + o http2: don't send a CURLE_SEND_ERROR after a valid http2 response [71] + o http2: initialize URL handle for server pushes [104] + o http2: update a refused stream's error after GOAWAY [102] o http: reject resumed uploads [16] + o httpsig-key.md: correct the generating Ed25519 keys commands [97] o imap: compare selected mailbox names case-sensitively [57] + o KNOWN_BUGS: drop the empty error buffer entry, it no longer happens [66] + o ldap: base64-encode a non-SAFE DN and reject a control-byte type name [55] o lib: connection shutdown, cleanup code [92] o lib: make private functions static and remove unused code [83] + o lib: reject internal handles in the multi/easy public API [103] o libssh2: match hashed known_hosts entries by host [30] o libssh2: skip repeat checkp() for already-absent hashed key types [82] o mbedtls: fix ticket handling [52] + o mime: bound Curl_mime_prepare_headers() recursion depth [76] o mime: do curl_mime_free() non-recursively [48] o mprintf: stage output and emit it in runs [38] o mulit_ev: remove socket references on forget [89] + o multi-event: fix timeout of pending transfers [111] o multi: reduce struct size with small cleanups [25] o multi: remove redundant completion message list [32] o multi: store timeout epoch as whole seconds [36] + o multi_ntfy: fix handling of more than 128 notifications in a batch [86] o multi_ntfy: replace enabled bitset with inline flags [44] o multihandle: some easy shrinks [33] o netrc: empty quoted values now produce a valid empty string [62] @@ -77,15 +91,24 @@ This release includes the following bugfixes: o openssl: use non-deprecated API for MD5/MD4 [69] o os400: document curl_formadd_CCSID multi-chunk peculiarity [5] o ossl_verifyhost: remove assumption of null termination of ASN1_STRING. [26] + o parsedate: find day names, months and time zones faster [68] + o perf-tests: output two-digital decimals always [96] + o pingpong: add a max loop counter for Curl_pp_readresp [113] + o rtsp: accept a Content-Length body on methods that expected none [115] + o rtsp: do not send a learned session id to another origin [10] o runtests: Added runnernums in order to have O(1) lookup [45] o schannel: auto_client_cert conn matching [17] o schannel: clear borrowed sslContext on close [15] o ssl: dedup ssl_fsslctx [35] + o telnet: make it work through proxy tunnels [112] o test 1605: move from unit to libtest [49] o test1399: improve reliability [9] o test1679: unset `SSL_CERT_FILE`, use `TESTNUMBER` [58] + o tftp: verify direction in a few places [101] o tool: (re)set SSL verification in --libcurl output [1] + o tool: buffer headers before remote filename selection [98] o tool_easysrc: reset the handle between --next operations [90] + o tool_help: guard category[2] access when category is bare "-" [59] o uint-bset: skip unused slots when counting o url: match connections without force reuse and candidates [95] o url: ntlm/negotiate, prohibit connection reuse on follows [18] @@ -116,13 +139,16 @@ Planned upcoming removals include: This release would not have looked like this without help, code, reports and advice from friends like these: - Artem Prilutskiy, Aviv Engelberg, Bobbeh Rhino, Christian Hesse, - Christian Ullrich, Claude, Daniel Lang, Daniel Stenberg, David Benjamin, - Florent Castelli, Fredrik Blau, Huang Yiheng, Joshua Rogers, Martin Dukek, - Max Dymond, Mihai-Gabriel Marin, Patrick Monnerat, rawsun007, Ray Satiro, - renovate[bot], Ron Kuper, Roshan Ramani, Rudolf Polzer, Stanislav Fort, - Stefan Eissing, Tyler Yankee, Un1q32, Viktor Szakats - (28 contributors) + Adam Shirt, Artem Prilutskiy, Aviv Engelberg, Axel Mierczuk, Bobbeh Rhino, + Christian Hesse, Christian Ullrich, Claude, Dan Fandrich, Daniel Lang, + Daniel Stenberg, David Benjamin, Fengxiaoxx on github, Florent Castelli, + Fredrik Blau, Huang Yiheng, Ilias Aberkane, Joshua Rogers, ldm0, Luna Tong, + Martin Dukek, Max Dymond, Mihai-Gabriel Marin, Muhamad Arga Reksapati, + Paolo Ganci, Patrick Monnerat, rawsun007, Ray Satiro, renovate[bot], + Ron Kuper, Roshan Ramani, Rudolf Polzer, Samuel Henrique, Skye Soss, + Stanislav Fort, Stefan Eissing, Stephen Psaradellis, szedenik-adam on github, + Thanniru Sai Teja, Tyler Yankee, Un1q32, Viktor Szakats, xhon-pelushi + (43 contributors) References to bug reports and discussions on issues: @@ -135,6 +161,7 @@ References to bug reports and discussions on issues: [7] = https://curl.se/bug/?i=22788 [8] = https://curl.se/bug/?i=22797 [9] = https://curl.se/bug/?i=22798 + [10] = https://curl.se/bug/?i=22920 [11] = https://curl.se/bug/?i=22791 [12] = https://curl.se/bug/?i=22795 [13] = https://curl.se/bug/?i=22786 @@ -179,28 +206,35 @@ References to bug reports and discussions on issues: [52] = https://curl.se/bug/?i=22871 [53] = https://curl.se/bug/?i=22870 [54] = https://curl.se/bug/?i=22924 + [55] = https://curl.se/bug/?i=22945 [56] = https://curl.se/bug/?i=22869 [57] = https://curl.se/bug/?i=22868 [58] = https://curl.se/bug/?i=22866 + [59] = https://curl.se/bug/?i=22957 [60] = https://curl.se/bug/?i=22862 [61] = https://curl.se/bug/?i=22860 [62] = https://issues.oss-fuzz.com/issues/560352522 [63] = https://curl.se/bug/?i=22652 [64] = https://curl.se/bug/?i=22819 [65] = https://curl.se/bug/?i=22849 + [66] = https://curl.se/bug/?i=544 [67] = https://curl.se/bug/?i=22839 + [68] = https://curl.se/bug/?i=22981 [69] = https://curl.se/bug/?i=22221 [70] = https://curl.se/bug/?i=22843 + [71] = https://curl.se/bug/?i=22984 [72] = https://curl.se/bug/?i=22911 [73] = https://curl.se/bug/?i=22910 [74] = https://curl.se/bug/?i=22896 [75] = https://curl.se/bug/?i=22900 + [76] = https://curl.se/bug/?i=22971 [78] = https://curl.se/bug/?i=22901 [79] = https://curl.se/bug/?i=22902 [80] = https://curl.se/bug/?i=22897 [81] = https://curl.se/bug/?i=22898 [82] = https://curl.se/bug/?i=22905 [83] = https://curl.se/bug/?i=22906 + [86] = https://curl.se/bug/?i=22963 [87] = https://curl.se/bug/?i=22899 [88] = https://curl.se/bug/?i=22903 [89] = https://curl.se/bug/?i=22895 @@ -209,3 +243,18 @@ References to bug reports and discussions on issues: [92] = https://curl.se/bug/?i=22888 [93] = https://curl.se/bug/?i=22889 [95] = https://curl.se/bug/?i=22857 + [96] = https://curl.se/bug/?i=22982 + [97] = https://curl.se/bug/?i=22961 + [98] = https://curl.se/bug/?i=22977 + [100] = https://curl.se/bug/?i=22969 + [101] = https://curl.se/bug/?i=22974 + [102] = https://curl.se/bug/?i=22923 + [103] = https://curl.se/bug/?i=22917 + [104] = https://curl.se/bug/?i=22970 + [105] = https://curl.se/bug/?i=22964 + [108] = https://curl.se/bug/?i=22916 + [109] = https://curl.se/bug/?i=22947 + [111] = https://curl.se/bug/?i=22942 + [112] = https://curl.se/bug/?i=22941 + [113] = https://curl.se/bug/?i=22932 + [115] = https://curl.se/bug/?i=12414 diff --git a/docs/THANKS-filter b/docs/THANKS-filter index f9e7e1c8d2d4..36cac5d9cb42 100644 --- a/docs/THANKS-filter +++ b/docs/THANKS-filter @@ -163,3 +163,4 @@ s/Qriist.*/Qriist on github/ s/Viktor Szakatas/Viktor Szakats/ s/Val S\./Valerie Snyder/ s/Andrew Nesbit$/Andrew Nesbitt/ +s/^https:\/\/.*// From ae08995ed1933e6d593fdcd80ae3a2c491d4569e Mon Sep 17 00:00:00 2001 From: Rhino <195818135+1rhino2@users.noreply.github.com> Date: Thu, 17 Sep 2026 18:52:07 -0400 Subject: [PATCH 160/481] tool_ipfs: keep the path percent-encoded when rewriting to the gateway The IPFS/IPNS rewrite fetched the user path (and the CID and gateway path) with CURLU_URLDECODE and then re-encoded the assembled path with CURLU_URLENCODE. The decode turned an encoded slash (%2f) into a real separator that the URL parser's earlier dot-segment cleanup never saw, so a ".." could climb back out of the // namespace when the final URL was parsed. A double-encoded form (%252e%252f) slipped through the same way. Build the new URL from the still-encoded parts instead: get the CID, the gateway path and the user path without decoding, and set the combined path without re-encoding. A percent-encoded slash then stays encoded, so no new separators appear and the later normalization has nothing extra to remove. A literal "../" in the user path is still collapsed by the parser when the ipfs:// URL is first parsed, so it resolves within the namespace as before. Add tests 1691 (ipfs) and 1692 (ipns) that send an encoded-slash dotdot path through a gateway with a base path and verify the request stays under //ipfs|ipns//. Both fail on the previous code with a request for /admin/config. Reported-by: 1rhino2 on hackerone Closes #22944 --- src/tool_ipfs.c | 16 +++++++++----- tests/data/Makefile.am | 2 ++ tests/data/test1691 | 49 ++++++++++++++++++++++++++++++++++++++++++ tests/data/test1692 | 49 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 111 insertions(+), 5 deletions(-) create mode 100644 tests/data/test1691 create mode 100644 tests/data/test1692 diff --git a/src/tool_ipfs.c b/src/tool_ipfs.c index 2abfcfd3db8d..2c58e65e653f 100644 --- a/src/tool_ipfs.c +++ b/src/tool_ipfs.c @@ -122,7 +122,8 @@ CURLcode ipfs_url_rewrite(CURLU *uh, const char *protocol, char **url, goto clean; } - getResult = curl_url_get(uh, CURLUPART_HOST, &cid, CURLU_URLDECODE); + /* keep the CID percent-encoded, it goes into the path (see note below) */ + getResult = curl_url_get(uh, CURLUPART_HOST, &cid, 0); if(getResult || !cid) goto clean; @@ -168,11 +169,16 @@ CURLcode ipfs_url_rewrite(CURLU *uh, const char *protocol, char **url, curl_url_get(gatewayurl, CURLUPART_SCHEME, &gwscheme, CURLU_URLDECODE) || curl_url_get(gatewayurl, CURLUPART_PORT, &gwport, CURLU_URLDECODE | CURLU_DEFAULT_PORT) || - curl_url_get(gatewayurl, CURLUPART_PATH, &gwpath, CURLU_URLDECODE)) + curl_url_get(gatewayurl, CURLUPART_PATH, &gwpath, 0)) goto clean; - /* get the path from user input */ - if(curl_url_get(uh, CURLUPART_PATH, &inputpath, CURLU_URLDECODE)) + /* Get the user path without decoding it, and build the new path below from + these still-encoded parts. Decoding here would turn an encoded slash (%2f) + into a real separator that the parser's earlier dot-segment cleanup never + saw, so a ".." could then climb out of the // namespace + when the final URL is parsed. Keeping every percent-encoded byte encoded + means no new separators appear and nothing new gets removed. */ + if(curl_url_get(uh, CURLUPART_PATH, &inputpath, 0)) goto clean; /* inputpath might be NULL or a valid pointer now */ @@ -191,7 +197,7 @@ CURLcode ipfs_url_rewrite(CURLU *uh, const char *protocol, char **url, protocol, cid, inputpath ? inputpath : ""); if(!pathbuffer || - curl_url_set(uh, CURLUPART_PATH, pathbuffer, CURLU_URLENCODE)) + curl_url_set(uh, CURLUPART_PATH, pathbuffer, 0)) goto clean; /* Free whatever it has now, rewriting is next */ diff --git a/tests/data/Makefile.am b/tests/data/Makefile.am index f743e0e77a18..f84145246fd9 100644 --- a/tests/data/Makefile.am +++ b/tests/data/Makefile.am @@ -1709,6 +1709,8 @@ TESTCASES = \ test1688 \ test1689 \ test1690 \ + test1691 \ + test1692 \ \ test1700 \ test1702 \ diff --git a/tests/data/test1691 b/tests/data/test1691 new file mode 100644 index 000000000000..d84bff7fc8de --- /dev/null +++ b/tests/data/test1691 @@ -0,0 +1,49 @@ + + + + +IPFS + + + +# Server-side + + +HTTP/1.1 200 OK +Date: Tue, 09 Nov 2010 14:49:00 GMT +Server: test-server/fake +Content-Length: 21 +Connection: close +Content-Type: text/plain + +Hello curl from IPFS + + + +# Client-side + + +ipfs + + +http + + +IPFS path with encoded slashes and dotdot stays under the gateway + + +--ipfs-gateway http://%HOSTIP:%HTTPPORT/private/gateway ipfs://bafybeidecnvkrygux6uoukouzps5ofkeevoqland7kopseiod6pzqvjg7u/%2f..%2f..%2f..%2f..%2f..%2fadmin/config + + + +# Verify data after the test has been "shot" + + +GET /private/gateway/ipfs/bafybeidecnvkrygux6uoukouzps5ofkeevoqland7kopseiod6pzqvjg7u/%2F..%2F..%2F..%2F..%2F..%2Fadmin/config HTTP/1.1 +Host: %HOSTIP:%HTTPPORT +User-Agent: curl/%VERSION +Accept: */* + + + + diff --git a/tests/data/test1692 b/tests/data/test1692 new file mode 100644 index 000000000000..74e7db98bdc4 --- /dev/null +++ b/tests/data/test1692 @@ -0,0 +1,49 @@ + + + + +IPNS + + + +# Server-side + + +HTTP/1.1 200 OK +Date: Tue, 09 Nov 2010 14:49:00 GMT +Server: test-server/fake +Content-Length: 21 +Connection: close +Content-Type: text/plain + +Hello curl from IPFS + + + +# Client-side + + +ipfs + + +http + + +IPNS path with encoded slashes and dotdot stays under the gateway + + +--ipfs-gateway http://%HOSTIP:%HTTPPORT/private/gateway ipns://bafybeidecnvkrygux6uoukouzps5ofkeevoqland7kopseiod6pzqvjg7u/%2f..%2f..%2f..%2f..%2f..%2fadmin/config + + + +# Verify data after the test has been "shot" + + +GET /private/gateway/ipns/bafybeidecnvkrygux6uoukouzps5ofkeevoqland7kopseiod6pzqvjg7u/%2F..%2F..%2F..%2F..%2F..%2Fadmin/config HTTP/1.1 +Host: %HOSTIP:%HTTPPORT +User-Agent: curl/%VERSION +Accept: */* + + + + From 55ecaa52b74e6333f8fdbbf5b85dd7507d8cf51b Mon Sep 17 00:00:00 2001 From: Joshua Rogers Date: Sat, 12 Sep 2026 18:48:35 +0200 Subject: [PATCH 161/481] cw-pause: fix O(N^2) list traversal in cw_pause_flush Switch the pause buffer list from prepend-at-head to append-at-tail and add a tail pointer to cw_pause_ctx. cw_pause_flush can now consume nodes from the head in O(1) per node instead of re-scanning from the head on every outer iteration. Closes #22968 --- lib/cw-pause.c | 48 +++++++++++++++++++++++++++--------------------- 1 file changed, 27 insertions(+), 21 deletions(-) diff --git a/lib/cw-pause.c b/lib/cw-pause.c index a5b9ba148e08..22c0f6e577fc 100644 --- a/lib/cw-pause.c +++ b/lib/cw-pause.c @@ -67,6 +67,7 @@ static void cw_pause_buf_free(struct cw_pause_buf *cwbuf) struct cw_pause_ctx { struct Curl_cwriter super; struct cw_pause_buf *buf; + struct cw_pause_buf *tail; size_t buf_total; }; @@ -76,6 +77,7 @@ static CURLcode cw_pause_init(struct Curl_easy *data, struct cw_pause_ctx *ctx = writer->ctx; (void)data; ctx->buf = NULL; + ctx->tail = NULL; return CURLE_OK; } @@ -86,6 +88,7 @@ static void cw_pause_bufs_free(struct cw_pause_ctx *ctx) cw_pause_buf_free(ctx->buf); ctx->buf = next; } + ctx->tail = NULL; } static void cw_pause_close(struct Curl_easy *data, struct Curl_cwriter *writer) @@ -102,39 +105,39 @@ static CURLcode cw_pause_flush(struct Curl_easy *data, struct cw_pause_ctx *ctx = (struct cw_pause_ctx *)cw_pause; CURLcode result = CURLE_OK; - /* write the end of the chain until it blocks or gets empty */ + /* write the front of the chain until it blocks or gets empty */ while(ctx->buf && !Curl_cwriter_is_paused(data)) { - struct cw_pause_buf **plast = &ctx->buf; + struct cw_pause_buf *first = ctx->buf; size_t blen, wlen = 0; const unsigned char *buf = NULL; - while((*plast)->next) /* got to last in list */ - plast = &(*plast)->next; - if(Curl_bufq_peek(&(*plast)->b, &buf, &blen)) { - wlen = ((*plast)->type & CLIENTWRITE_BODY) ? + if(Curl_bufq_peek(&first->b, &buf, &blen)) { + wlen = (first->type & CLIENTWRITE_BODY) ? CURLMIN(blen, CW_PAUSE_DEC_WRITE_CHUNK) : blen; - result = Curl_cwriter_write(data, cw_pause->next, (*plast)->type, + result = Curl_cwriter_write(data, cw_pause->next, first->type, (const char *)buf, wlen); CURL_TRC_WRITE(data, "[PAUSE] flushed %zu/%zu bytes, type=%x -> %d", - wlen, ctx->buf_total, (unsigned int)(*plast)->type, + wlen, ctx->buf_total, (unsigned int)first->type, (int)result); - Curl_bufq_skip(&(*plast)->b, wlen); + Curl_bufq_skip(&first->b, wlen); DEBUGASSERT(ctx->buf_total >= wlen); ctx->buf_total -= wlen; if(result) return result; } - else if((*plast)->type & CLIENTWRITE_EOS) { - result = Curl_cwriter_write(data, cw_pause->next, (*plast)->type, + else if(first->type & CLIENTWRITE_EOS) { + result = Curl_cwriter_write(data, cw_pause->next, first->type, (const char *)buf, 0); CURL_TRC_WRITE(data, "[PAUSE] flushed 0/%zu bytes, type=%x -> %d", - ctx->buf_total, (unsigned int)(*plast)->type, + ctx->buf_total, (unsigned int)first->type, (int)result); } - if(Curl_bufq_is_empty(&(*plast)->b)) { - cw_pause_buf_free(*plast); - *plast = NULL; + if(Curl_bufq_is_empty(&first->b)) { + ctx->buf = first->next; + if(!ctx->buf) + ctx->tail = NULL; + cw_pause_buf_free(first); } } @@ -177,19 +180,22 @@ static CURLcode cw_pause_write(struct Curl_easy *data, do { size_t nwritten = 0; - if(ctx->buf && (ctx->buf->type == type) && (type & CLIENTWRITE_BODY)) { - /* same type and body, append to current buffer which has a soft + if(ctx->tail && (ctx->tail->type == type) && (type & CLIENTWRITE_BODY)) { + /* same type and body, append to tail buffer which has a soft * limit and should take everything up to OOM. */ - result = Curl_bufq_cwrite(&ctx->buf->b, buf, blen, &nwritten); + result = Curl_bufq_cwrite(&ctx->tail->b, buf, blen, &nwritten); } else { /* Need a new buf, type changed */ struct cw_pause_buf *cwbuf = cw_pause_buf_create(type, blen); if(!cwbuf) return CURLE_OUT_OF_MEMORY; - cwbuf->next = ctx->buf; - ctx->buf = cwbuf; - result = Curl_bufq_cwrite(&ctx->buf->b, buf, blen, &nwritten); + if(ctx->tail) + ctx->tail->next = cwbuf; + else + ctx->buf = cwbuf; + ctx->tail = cwbuf; + result = Curl_bufq_cwrite(&ctx->tail->b, buf, blen, &nwritten); } CURL_TRC_WRITE(data, "[PAUSE] buffer %zu more bytes of type %x, " "total=%zu -> %d", nwritten, (unsigned int)type, From 08d0991563537cd375c89339ad4c6e79e8f8d404 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 16 Sep 2026 04:44:11 +0000 Subject: [PATCH 162/481] GHA: update pizlonator/fil-c to v0.685 Closes #22962 --- .github/workflows/linux.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index e728e44a489e..f5ad601ab587 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -42,7 +42,7 @@ env: # renovate: datasource=github-tags depName=google/boringssl versioning=semver registryUrl=https://github.com BORINGSSL_VERSION: 0.20260903.0 # renovate: datasource=github-releases depName=pizlonator/fil-c versioning=semver-coerced registryUrl=https://github.com - FIL_C_VERSION: 0.684 + FIL_C_VERSION: 0.685 # renovate: datasource=github-tags depName=libressl/portable versioning=semver registryUrl=https://github.com LIBRESSL_VERSION: 4.3.2 # renovate: datasource=github-tags depName=Mbed-TLS/mbedtls versioning=semver registryUrl=https://github.com From 961a8fd0abb742df6c9f6f597ce3d1b17903ae97 Mon Sep 17 00:00:00 2001 From: Daniel Stenberg Date: Fri, 18 Sep 2026 07:58:54 +0200 Subject: [PATCH 163/481] curl/curlver.h: bump to 8.23.0 --- RELEASE-NOTES | 2 +- include/curl/curlver.h | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/RELEASE-NOTES b/RELEASE-NOTES index 7dce782a2604..71d16cbcdb99 100644 --- a/RELEASE-NOTES +++ b/RELEASE-NOTES @@ -1,4 +1,4 @@ -curl and libcurl 8.22.1 +curl and libcurl 8.23.0 Public curl releases: 276 Command line options: 278 diff --git a/include/curl/curlver.h b/include/curl/curlver.h index 65390e52fe80..c0370bd57515 100644 --- a/include/curl/curlver.h +++ b/include/curl/curlver.h @@ -32,13 +32,13 @@ /* This is the version number of the libcurl package from which this header file origins: */ -#define LIBCURL_VERSION "8.22.1-DEV" +#define LIBCURL_VERSION "8.23.0-DEV" /* The numeric version number is also available "in parts" by using these defines: */ #define LIBCURL_VERSION_MAJOR 8 -#define LIBCURL_VERSION_MINOR 22 -#define LIBCURL_VERSION_PATCH 1 +#define LIBCURL_VERSION_MINOR 23 +#define LIBCURL_VERSION_PATCH 0 /* This is the numeric version of the libcurl version number, meant for easier parsing and comparisons by programs. The LIBCURL_VERSION_NUM define always follows this syntax: @@ -57,7 +57,7 @@ Note: This define is the full hex number and _does not_ use the CURL_VERSION_BITS() macro since curl's own configure script greps for it and needs it to contain the full number. */ -#define LIBCURL_VERSION_NUM 0x081601 +#define LIBCURL_VERSION_NUM 0x081700 /* * This is the date and time when the full source package was created. The From 01eff554af43e65bc4910f79c41e3090f46ac451 Mon Sep 17 00:00:00 2001 From: Stefan Eissing Date: Thu, 17 Sep 2026 15:01:53 +0200 Subject: [PATCH 164/481] u32_ptrset: a set of uint32_t + void* values This replaces uint32_hash as a leaner and faster implementation. It uses the same internal logic as u8_strset for hashing ids. Use in http2 and QUIC implementations to connect `data->mid` to the `stream` struct of the protocol implementation. Unit test in test3211 Remove uint-hashset.[ch] and its unit test. Closes #22986 --- lib/Makefile.inc | 4 +- lib/http2.c | 16 +-- lib/u32_ptrset.c | 277 ++++++++++++++++++++++++++++++++++++++ lib/u32_ptrset.h | 70 ++++++++++ lib/uint-hash.c | 235 -------------------------------- lib/uint-hash.h | 59 -------- lib/vquic/cf-ngtcp2-cmn.c | 20 +-- lib/vquic/cf-ngtcp2-cmn.h | 4 +- lib/vquic/cf-ngtcp2.c | 2 +- lib/vquic/cf-quiche.c | 20 +-- lib/vquic/vquic_int.h | 3 +- tests/data/Makefile.am | 1 - tests/data/test1616 | 19 --- tests/data/test3211 | 3 +- tests/unit/Makefile.inc | 1 - tests/unit/unit1399.c | 2 +- tests/unit/unit1616.c | 85 ------------ tests/unit/unit3211.c | 60 ++++++++- 18 files changed, 444 insertions(+), 437 deletions(-) create mode 100644 lib/u32_ptrset.c create mode 100644 lib/u32_ptrset.h delete mode 100644 lib/uint-hash.c delete mode 100644 lib/uint-hash.h delete mode 100644 tests/data/test1616 delete mode 100644 tests/unit/unit1616.c diff --git a/lib/Makefile.inc b/lib/Makefile.inc index 301708da23ef..183cf37a40f7 100644 --- a/lib/Makefile.inc +++ b/lib/Makefile.inc @@ -289,8 +289,8 @@ LIB_CFILES = \ thrdqueue.c \ transfer.c \ u8_strset.c \ + u32_ptrset.c \ uint-bset.c \ - uint-hash.c \ uint-spbset.c \ uint-table.c \ url.c \ @@ -421,8 +421,8 @@ LIB_HFILES = \ thrdqueue.h \ transfer.h \ u8_strset.h \ + u32_ptrset.h \ uint-bset.h \ - uint-hash.h \ uint-spbset.h \ uint-table.h \ url.h \ diff --git a/lib/http2.c b/lib/http2.c index be211b085693..740e58317889 100644 --- a/lib/http2.c +++ b/lib/http2.c @@ -28,7 +28,7 @@ #include "urldata.h" #include "bufq.h" -#include "uint-hash.h" +#include "u32_ptrset.h" #include "http1.h" #include "http2.h" #include "http.h" @@ -97,7 +97,7 @@ struct cf_h2_ctx { struct bufc_pool stream_bufcp; /* spares for stream buffers */ struct dynbuf scratch; /* scratch buffer for temp use */ - struct uint_hash streams; /* hash of `data->mid` to `h2_stream_ctx` */ + struct u32_ptrset streams; /* hash of `data->mid` to `h2_stream_ctx` */ size_t drain_total; /* sum of all stream's UrlState drain */ uint32_t initial_win_size; /* current initial window size (settings) */ uint32_t max_concurrent_streams; @@ -165,7 +165,7 @@ static void h2_stream_ctx_free(struct h2_stream_ctx *stream) curlx_free(stream); } -static void h2_stream_hash_free(unsigned int id, void *stream) +static void h2_stream_hash_free(uint32_t id, void *stream) { (void)id; DEBUGASSERT(stream); @@ -178,7 +178,7 @@ static void cf_h2_ctx_init(struct cf_h2_ctx *ctx, bool via_h1_upgrade) Curl_bufq_initp(&ctx->inbufq, &ctx->stream_bufcp, H2_NW_RECV_CHUNKS, 0); Curl_bufq_initp(&ctx->outbufq, &ctx->stream_bufcp, H2_NW_SEND_CHUNKS, 0); curlx_dyn_init(&ctx->scratch, CURL_MAX_HTTP_HEADER); - Curl_uint32_hash_init(&ctx->streams, 63, h2_stream_hash_free); + Curl_u32_ptrset_init(&ctx->streams, h2_stream_hash_free); ctx->remote_max_sid = INT32_MAX; ctx->via_h1_upgrade = via_h1_upgrade; ctx->initialized = TRUE; @@ -193,7 +193,7 @@ static void cf_h2_ctx_free(struct cf_h2_ctx *ctx) Curl_bufq_free(&ctx->outbufq); Curl_bufcp_free(&ctx->stream_bufcp); curlx_dyn_free(&ctx->scratch); - Curl_uint32_hash_destroy(&ctx->streams); + Curl_u32_ptrset_clear(&ctx->streams); memset(ctx, 0, sizeof(*ctx)); } curlx_free(ctx); @@ -255,7 +255,7 @@ static CURLcode cf_h2_update_settings(struct cf_h2_ctx *ctx, #define H2_STREAM_CTX(ctx, data) \ ((struct h2_stream_ctx *)( \ - (data) ? Curl_uint32_hash_get(&(ctx)->streams, (data)->mid) : NULL)) + (data) ? Curl_u32_ptrset_get(&(ctx)->streams, (data)->mid) : NULL)) static struct h2_stream_ctx *h2_stream_ctx_create(struct cf_h2_ctx *ctx) { @@ -381,7 +381,7 @@ static CURLcode http2_data_setup(struct Curl_cfilter *cf, if(!stream) return CURLE_OUT_OF_MEMORY; - if(!Curl_uint32_hash_set(&ctx->streams, data->mid, stream)) { + if(Curl_u32_ptrset_set(&ctx->streams, data->mid, stream)) { h2_stream_ctx_free(stream); return CURLE_OUT_OF_MEMORY; } @@ -444,7 +444,7 @@ static void http2_data_done(struct Curl_cfilter *cf, struct Curl_easy *data) } } - Curl_uint32_hash_remove(&ctx->streams, data->mid); + Curl_u32_ptrset_unset(&ctx->streams, data->mid); } static int h2_client_new(struct Curl_cfilter *cf, diff --git a/lib/u32_ptrset.c b/lib/u32_ptrset.c new file mode 100644 index 000000000000..fe0f685d8f0b --- /dev/null +++ b/lib/u32_ptrset.c @@ -0,0 +1,277 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ +#include "curl_setup.h" + +#include "u32_ptrset.h" + +/* random patterns for API verification */ +#ifdef DEBUGBUILD +#define CURL_U32_PTRSET_MAGIC 0x75703272 +#endif + +#define CURL_U32_PTRSET_DEBUG 0 + +#ifndef CURL_SWAP +#define CURL_SWAP(a, b) (((a) ^= (b)), ((b) ^= (a)), ((a) ^= (b))) +#endif + +#define CURL_U32_SLOT_CNT(bits) ((bits) ? (1U << (bits)) : 0) +#define CURL_U32_PTRSET_SLOT_CNT(s) CURL_U32_SLOT_CNT((s)->slotbits) +#define CURL_U32_PTRSET_SLOT_IDX(s, i) (uint32_t)((i) & (s)->slotmask) + +/* A hashset for tuples (id, ptr) using Robin Hood Hashing. + * + * The basic idea here to handle collisions by robbing "rich" entries and + * giving to the "poor": + * - We have an array: (id, ptr) are ideally placed at index "id % size". + * - If slot at index is already occupied, we have a collision. + * - A simple collision strategy would look at the next index, and the + * next until finding an empty slot. + * - The drawback is that this may lead to many checks on lookups, as it + * will need to also look at subsequent slots until it finds the match. + * The amount of lookups is the "probe sequence length" (psl) and this + * may vary greatly between entries. + * - Robin Hood Hashing balances the 'psl's of all entries more evenly: + * - psl == 0 means an entry is in exactly the right slot + * - psl == 1 means it is in the slot right after. psl == 2 is the slot + * after that, etc. + * - when inserting a new entry, track its psl. Finding a slot where + * the existing entry has a lower psl makes a swap. Put the new entry + * and its psl there, take the previous entry and its psl and find + * the next best slot for the previous entry. */ +void Curl_u32_ptrset_init(struct u32_ptrset *set, + Curl_u32_ptrset_dtor *dtor) +{ + memset(set, 0, sizeof(*set)); + set->dtor = dtor; +#ifdef DEBUGBUILD + set->init = CURL_U32_PTRSET_MAGIC; +#endif +} + +void Curl_u32_ptrset_clear(struct u32_ptrset *set) +{ + uint32_t i; + DEBUGASSERT(set->init == CURL_U32_PTRSET_MAGIC); + if(set->dtor) + for(i = 0; i < CURL_U32_PTRSET_SLOT_CNT(set); ++i) + if(set->data[i]) + set->dtor(set->ids[i], set->data[i]); + + curlx_safefree(set->data); + Curl_u32_ptrset_init(set, set->dtor); +} + +static void u32_ptrset_add(struct u32_ptrset *set, uint32_t id, void *ptr) +{ + uint32_t i = CURL_U32_PTRSET_SLOT_IDX(set, id); + uint32_t psl = 0; + while(set->data[i]) { + if(psl > set->psl[i]) { /* SWAP */ + void *tmpdata; +#if CURL_U32_PTRSET_DEBUG + curl_mfprintf(stderr, "u32_ptrset_add %u=%p to idx=%u\n", id, ptr, i); +#endif + tmpdata = set->data[i]; + set->data[i] = ptr; + ptr = tmpdata; + CURL_SWAP(set->psl[i], psl); + CURL_SWAP(set->ids[i], id); + } + i = CURL_U32_PTRSET_SLOT_IDX(set, i + 1); + ++psl; + } + set->ids[i] = id; + set->data[i] = ptr; + set->psl[i] = psl; + ++set->count; +#if CURL_U32_PTRSET_DEBUG + curl_mfprintf(stderr, "u32_ptrset_add %u=%p to idx=%u\n", id, ptr, i); +#endif +} + +static bool u32_ptrset_grow(struct u32_ptrset *set) +{ + uint32_t i, *prev_ids; + uint8_t nslotbits, pslotbits; + void **prev_data; + size_t nslots; + void *d; + + if(set->slotbits >= 32) + return FALSE; + nslotbits = set->slotbits ? (uint8_t)(set->slotbits + 1) : 5; + nslots = CURL_U32_SLOT_CNT(nslotbits); +#if CURL_U32_PTRSET_DEBUG + curl_mfprintf(stderr, "u32_ptrset_grow from bits=%d to %d, slots=%zu\n", + set->slotbits, nslotbits, nslots); +#endif + d = curlx_calloc(1, (nslots * sizeof(void *)) + + (2 * nslots * sizeof(uint32_t))); + if(!d) + return FALSE; + + prev_data = set->data; + prev_ids = set->ids; + pslotbits = set->slotbits; +#if defined(__GNUC__) && __GNUC__ >= 13 +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wanalyzer-allocation-size" +#endif + set->data = d; + set->ids = (uint32_t *)(set->data + nslots); + set->psl = (uint32_t *)(set->ids + nslots); +#if defined(__GNUC__) && __GNUC__ >= 13 +#pragma GCC diagnostic pop +#endif + + set->slotbits = nslotbits; + set->slotmask = (uint32_t)((1U << set->slotbits) - 1); + set->count = 0; + /* re-add previous entries */ + for(i = 0; i < CURL_U32_SLOT_CNT(pslotbits); ++i) { + if(prev_data[i]) + u32_ptrset_add(set, prev_ids[i], prev_data[i]); + } + if(prev_data) + curlx_free(prev_data); + return TRUE; +} + +static bool u32_ptrset_get_index(struct u32_ptrset *set, + uint32_t id, uint32_t *pindex) +{ + uint32_t i = CURL_U32_PTRSET_SLOT_IDX(set, id); + uint32_t psl = 0; + if(!set->data) + return FALSE; + while(set->data[i] && (psl <= set->psl[i])) { + if(set->ids[i] == id) { + *pindex = i; +#if CURL_U32_PTRSET_DEBUG + curl_mfprintf(stderr, "u32_ptrset_index %u=%p at idx=%u\n", + id, set->data[i], i); +#endif + return TRUE; + } + i = CURL_U32_PTRSET_SLOT_IDX(set, i + 1); + ++psl; + } +#if CURL_U32_PTRSET_DEBUG + curl_mfprintf(stderr, "u32_ptrset_index %u not found\n", id); +#endif + *pindex = 0; + return FALSE; +} + +uint32_t Curl_u32_ptrset_count(struct u32_ptrset *set) +{ + return set->count; +} + +void *Curl_u32_ptrset_get(struct u32_ptrset *set, uint32_t id) +{ + uint32_t i; + DEBUGASSERT(set->init == CURL_U32_PTRSET_MAGIC); + if(u32_ptrset_get_index(set, id, &i)) + return set->data[i]; + return NULL; +} + +CURLcode Curl_u32_ptrset_set(struct u32_ptrset *set, + uint32_t id, void *ptr) +{ + uint32_t i; + + DEBUGASSERT(set->init == CURL_U32_PTRSET_MAGIC); + if(!ptr) { + Curl_u32_ptrset_unset(set, id); + return CURLE_OK; + } + + if(u32_ptrset_get_index(set, id, &i)) { + /* `id` is in set, replace value */ + if(set->dtor && (set->data[i] != ptr)) + set->dtor(set->ids[i], set->data[i]); +#if CURL_U32_PTRSET_DEBUG + curl_mfprintf(stderr, "u32_ptrset_set replace %u=%p at idx=%u\n", + id, ptr, i); +#endif + set->data[i] = ptr; + return CURLE_OK; + } + /* `id` not in set yet, grow if full */ + if((set->count >= CURL_U32_PTRSET_SLOT_CNT(set)) && + !u32_ptrset_grow(set)) { + return CURLE_OUT_OF_MEMORY; + } + + u32_ptrset_add(set, id, ptr); + return CURLE_OK; +} + +static void u32_ptrset_unset(struct u32_ptrset *set, uint32_t id) +{ + uint32_t i, j; + + DEBUGASSERT(set->init == CURL_U32_PTRSET_MAGIC); + if(u32_ptrset_get_index(set, id, &i)) { + /* `id` is in set */ +#if CURL_U32_PTRSET_DEBUG + curl_mfprintf(stderr, "u32_ptrset_unset %u at idx=%u\n", id, i); +#endif + if(set->dtor) + set->dtor(set->ids[i], set->data[i]); + set->data[i] = NULL; + set->ids[i] = set->psl[i] = 0; + --set->count; + j = CURL_U32_PTRSET_SLOT_IDX(set, i + 1); + /* shift all entries with positive psl "down" */ + while(set->data[j] && set->psl[j]) { + set->data[i] = set->data[j]; + set->ids[i] = set->ids[j]; + set->psl[i] = set->psl[j] - 1; + set->data[j] = NULL; + set->ids[j] = set->psl[j] = 0; + i = j; + j = CURL_U32_PTRSET_SLOT_IDX(set, i + 1); + } + } +} + +void Curl_u32_ptrset_unset(struct u32_ptrset *set, uint32_t id) +{ + u32_ptrset_unset(set, id); +} + +void Curl_u32_ptrset_visit(struct u32_ptrset *set, + Curl_u32_ptrset_visit_cb *cb, + void *user_data) +{ + uint32_t i; + for(i = 0; i < CURL_U32_PTRSET_SLOT_CNT(set); ++i) { + if(set->data[i] && !cb(set->ids[i], set->data[i], user_data)) + break; + } +} diff --git a/lib/u32_ptrset.h b/lib/u32_ptrset.h new file mode 100644 index 000000000000..6d6868660f55 --- /dev/null +++ b/lib/u32_ptrset.h @@ -0,0 +1,70 @@ +#ifndef HEADER_CURL_U32_PTRSET_H +#define HEADER_CURL_U32_PTRSET_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ +#include "curl_setup.h" + +typedef void Curl_u32_ptrset_dtor(uint32_t id, void *ptr); + +/* A set that can hold up UINT32_MAX pointers identified by an `id'. + * Setting a pointer for an existing id replaces the previous one. + * Getting the pointer for an id not in the set returns NULL. + * Setting an id to NULL unsets the id. + */ +struct u32_ptrset { + void **data; /* #slots array of pointers */ + uint32_t *ids; /* #slots array of `id` values */ + uint32_t *psl; /* #slots array of "probe sequence length" values */ + uint32_t slotmask; + uint32_t count; + Curl_u32_ptrset_dtor *dtor; + uint8_t slotbits; +#ifdef DEBUGBUILD + int32_t init; +#endif +}; + +void Curl_u32_ptrset_init(struct u32_ptrset *set, + Curl_u32_ptrset_dtor *dtor); +void Curl_u32_ptrset_clear(struct u32_ptrset *set); + +uint32_t Curl_u32_ptrset_count(struct u32_ptrset *set); + +void *Curl_u32_ptrset_get(struct u32_ptrset *set, uint32_t id); + +/* Set pointer for id. */ +CURLcode Curl_u32_ptrset_set(struct u32_ptrset *set, + uint32_t id, void *ptr); + +void Curl_u32_ptrset_unset(struct u32_ptrset *set, uint32_t id); + +typedef bool Curl_u32_ptrset_visit_cb(uint32_t id, void *ptr, + void *user_data); + +/* Visit all entries in the set until the callback returns FALSE */ +void Curl_u32_ptrset_visit(struct u32_ptrset *set, + Curl_u32_ptrset_visit_cb *cb, + void *user_data); + +#endif /* HEADER_CURL_U32_PTRSET_H */ diff --git a/lib/uint-hash.c b/lib/uint-hash.c deleted file mode 100644 index c37448fd1130..000000000000 --- a/lib/uint-hash.c +++ /dev/null @@ -1,235 +0,0 @@ -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) Daniel Stenberg, , et al. - * - * This software is licensed as described in the file COPYING, which - * you should have received as part of this distribution. The terms - * are also available at https://curl.se/docs/copyright.html. - * - * You may opt to use, copy, modify, merge, publish, distribute and/or sell - * copies of the Software, and permit persons to whom the Software is - * furnished to do so, under the terms of the COPYING file. - * - * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY - * KIND, either express or implied. - * - * SPDX-License-Identifier: curl - * - ***************************************************************************/ -#include "curl_setup.h" - -#include "uint-hash.h" - -/* random patterns for API verification */ -#ifdef DEBUGBUILD -#define CURL_UINT32_HASHINIT 0x7117e779 -#endif - -static uint32_t uint32_hash_hash(uint32_t id, uint32_t slots) -{ - return (id % slots); -} - -struct uint_hash_entry { - struct uint_hash_entry *next; - void *value; - uint32_t id; -}; - -void Curl_uint32_hash_init(struct uint_hash *h, - uint32_t slots, - Curl_uint32_hash_dtor *dtor) -{ - DEBUGASSERT(h); - DEBUGASSERT(slots); - - h->table = NULL; - h->dtor = dtor; - h->size = 0; - h->slots = slots; -#ifdef DEBUGBUILD - h->init = CURL_UINT32_HASHINIT; -#endif -} - -static struct uint_hash_entry *uint32_hash_mk_entry(uint32_t id, void *value) -{ - struct uint_hash_entry *e; - - /* allocate the struct for the hash entry */ - e = curlx_malloc(sizeof(*e)); - if(e) { - e->id = id; - e->next = NULL; - e->value = value; - } - return e; -} - -static void uint32_hash_entry_clear(struct uint_hash *h, - struct uint_hash_entry *e) -{ - DEBUGASSERT(h); - DEBUGASSERT(e); - if(e->value) { - if(h->dtor) - h->dtor(e->id, e->value); - e->value = NULL; - } -} - -static void uint32_hash_entry_destroy(struct uint_hash *h, - struct uint_hash_entry *e) -{ - uint32_hash_entry_clear(h, e); - curlx_free(e); -} - -static void uint32_hash_entry_unlink(struct uint_hash *h, - struct uint_hash_entry **he_anchor, - struct uint_hash_entry *he) -{ - *he_anchor = he->next; - --h->size; -} - -static void uint32_hash_elem_link(struct uint_hash *h, - struct uint_hash_entry **he_anchor, - struct uint_hash_entry *he) -{ - he->next = *he_anchor; - *he_anchor = he; - ++h->size; -} - -#define CURL_UINT32_HASH_SLOT(h, id) h->table[uint32_hash_hash(id, (h)->slots)] -#define CURL_UINT32_HASH_SLOT_ADDR(h, id) &CURL_UINT32_HASH_SLOT(h, id) - -bool Curl_uint32_hash_set(struct uint_hash *h, uint32_t id, void *value) -{ - struct uint_hash_entry *he, **slot; - - DEBUGASSERT(h); - DEBUGASSERT(h->slots); - DEBUGASSERT(h->init == CURL_UINT32_HASHINIT); - if(!h->table) { - h->table = curlx_calloc(h->slots, sizeof(*he)); - if(!h->table) - return FALSE; /* OOM */ - } - - slot = CURL_UINT32_HASH_SLOT_ADDR(h, id); - for(he = *slot; he; he = he->next) { - if(he->id == id) { - /* existing key entry, overwrite by clearing old pointer */ - uint32_hash_entry_clear(h, he); - he->value = value; - return TRUE; - } - } - - he = uint32_hash_mk_entry(id, value); - if(!he) - return FALSE; /* OOM */ - - uint32_hash_elem_link(h, slot, he); - return TRUE; -} - -bool Curl_uint32_hash_remove(struct uint_hash *h, uint32_t id) -{ - DEBUGASSERT(h); - DEBUGASSERT(h->slots); - DEBUGASSERT(h->init == CURL_UINT32_HASHINIT); - if(h->table) { - struct uint_hash_entry *he, **he_anchor; - - he_anchor = CURL_UINT32_HASH_SLOT_ADDR(h, id); - while(*he_anchor) { - he = *he_anchor; - if(id == he->id) { - uint32_hash_entry_unlink(h, he_anchor, he); - uint32_hash_entry_destroy(h, he); - return TRUE; - } - he_anchor = &he->next; - } - } - return FALSE; -} - -void *Curl_uint32_hash_get(struct uint_hash *h, uint32_t id) -{ - DEBUGASSERT(h); - DEBUGASSERT(h->init == CURL_UINT32_HASHINIT); - if(h->table) { - struct uint_hash_entry *he; - DEBUGASSERT(h->slots); - he = CURL_UINT32_HASH_SLOT(h, id); - while(he) { - if(id == he->id) { - return he->value; - } - he = he->next; - } - } - return NULL; -} - -/* @unittest 1616 */ -UNITTEST void uint_hash_clear(struct uint_hash *h); -UNITTEST void uint_hash_clear(struct uint_hash *h) -{ - if(h && h->table) { - struct uint_hash_entry *he, **he_anchor; - size_t i; - DEBUGASSERT(h->init == CURL_UINT32_HASHINIT); - for(i = 0; i < h->slots; ++i) { - he_anchor = &h->table[i]; - while(*he_anchor) { - he = *he_anchor; - uint32_hash_entry_unlink(h, he_anchor, he); - uint32_hash_entry_destroy(h, he); - } - } - } -} - -void Curl_uint32_hash_destroy(struct uint_hash *h) -{ - DEBUGASSERT(h->init == CURL_UINT32_HASHINIT); - if(h->table) { - uint_hash_clear(h); - curlx_safefree(h->table); - } - DEBUGASSERT(h->size == 0); - h->slots = 0; -} - -uint32_t Curl_uint32_hash_count(struct uint_hash *h) -{ - DEBUGASSERT(h->init == CURL_UINT32_HASHINIT); - return h->size; -} - -void Curl_uint32_hash_visit(struct uint_hash *h, - Curl_uint32_hash_visit_cb *cb, - void *user_data) -{ - if(h && h->table && cb) { - struct uint_hash_entry *he; - size_t i; - DEBUGASSERT(h->init == CURL_UINT32_HASHINIT); - for(i = 0; i < h->slots; ++i) { - for(he = h->table[i]; he; he = he->next) { - if(!cb(he->id, he->value, user_data)) - return; - } - } - } -} diff --git a/lib/uint-hash.h b/lib/uint-hash.h deleted file mode 100644 index 3cbcf8f8f11f..000000000000 --- a/lib/uint-hash.h +++ /dev/null @@ -1,59 +0,0 @@ -#ifndef HEADER_CURL_UINT_HASH_H -#define HEADER_CURL_UINT_HASH_H -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) Daniel Stenberg, , et al. - * - * This software is licensed as described in the file COPYING, which - * you should have received as part of this distribution. The terms - * are also available at https://curl.se/docs/copyright.html. - * - * You may opt to use, copy, modify, merge, publish, distribute and/or sell - * copies of the Software, and permit persons to whom the Software is - * furnished to do so, under the terms of the COPYING file. - * - * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY - * KIND, either express or implied. - * - * SPDX-License-Identifier: curl - * - ***************************************************************************/ -#include "curl_setup.h" - -/* A version with uint32_t as key */ -typedef void Curl_uint32_hash_dtor(uint32_t id, void *value); -struct uint_hash_entry; - -/* Hash for `uint32_t` as key */ -struct uint_hash { - struct uint_hash_entry **table; - Curl_uint32_hash_dtor *dtor; - uint32_t slots; - uint32_t size; -#ifdef DEBUGBUILD - int init; -#endif -}; - -void Curl_uint32_hash_init(struct uint_hash *h, - uint32_t slots, - Curl_uint32_hash_dtor *dtor); -void Curl_uint32_hash_destroy(struct uint_hash *h); -bool Curl_uint32_hash_set(struct uint_hash *h, uint32_t id, void *value); -bool Curl_uint32_hash_remove(struct uint_hash *h, uint32_t id); -void *Curl_uint32_hash_get(struct uint_hash *h, uint32_t id); -uint32_t Curl_uint32_hash_count(struct uint_hash *h); - -typedef bool Curl_uint32_hash_visit_cb(uint32_t id, void *value, - void *user_data); - -void Curl_uint32_hash_visit(struct uint_hash *h, - Curl_uint32_hash_visit_cb *cb, - void *user_data); - -#endif /* HEADER_CURL_UINT_HASH_H */ diff --git a/lib/vquic/cf-ngtcp2-cmn.c b/lib/vquic/cf-ngtcp2-cmn.c index 2b6cec68b0f7..38acff006569 100644 --- a/lib/vquic/cf-ngtcp2-cmn.c +++ b/lib/vquic/cf-ngtcp2-cmn.c @@ -49,7 +49,7 @@ #include "urldata.h" #include "url.h" -#include "uint-hash.h" +#include "u32_ptrset.h" #include "curl_trc.h" #include "rand.h" #include "multiif.h" @@ -90,7 +90,7 @@ void Curl_cf_ngtcp2_h3_stream_ctx_free(struct h3_stream_ctx *stream) curlx_free(stream); } -static void h3_stream_hash_free(unsigned int id, void *stream) +static void h3_stream_set_free(uint32_t id, void *stream) { (void)id; DEBUGASSERT(stream); @@ -129,7 +129,7 @@ CURLcode Curl_cf_ngtcp2_ctx_init(struct cf_ngtcp2_ctx *ctx, Curl_bufcp_init(&ctx->stream_bufcp, H3_STREAM_CHUNK_SIZE, H3_STREAM_POOL_SPARES); curlx_dyn_init(&ctx->scratch, CURL_MAX_HTTP_HEADER); - Curl_uint32_hash_init(&ctx->streams, 63, h3_stream_hash_free); + Curl_u32_ptrset_init(&ctx->streams, h3_stream_set_free); ctx->init_h3_conn_cb = init_h3_conn_cb; ctx->initialized = TRUE; return Curl_vquic_tls_peer_init(origin, peer, sslc, &ctx->ssl_peer); @@ -142,7 +142,7 @@ void Curl_cf_ngtcp2_ctx_cleanup(struct cf_ngtcp2_ctx *ctx) Curl_vquic_ctx_free(&ctx->q); Curl_bufcp_free(&ctx->stream_bufcp); curlx_dyn_free(&ctx->scratch); - Curl_uint32_hash_destroy(&ctx->streams); + Curl_u32_ptrset_clear(&ctx->streams); Curl_ssl_peer_cleanup(&ctx->ssl_peer); curlx_safefree(ctx->tunnel_inbuf); ctx->tunnel_inbuf_len = 0; @@ -1399,7 +1399,7 @@ static struct h3_stream_ctx *cf_ngtcp2_get_stream(struct cf_ngtcp2_ctx *ctx, struct cf_ngtcp2_sfind_ctx fctx; fctx.stream_id = stream_id; fctx.stream = NULL; - Curl_uint32_hash_visit(&ctx->streams, cf_ngtcp2_sfind, &fctx); + Curl_u32_ptrset_visit(&ctx->streams, cf_ngtcp2_sfind, &fctx); return fctx.stream; } #else @@ -1857,7 +1857,7 @@ static void cf_ngtcp2_setup_keep_alive(struct Curl_cfilter *cf, ngtcp2_conn_set_keep_alive_timeout(ctx->qconn, UINT64_MAX); CURL_TRC_CF(data, cf, "no peer idle timeout, unset keep-alive"); } - else if(!Curl_uint32_hash_count(&ctx->streams)) { + else if(!Curl_u32_ptrset_count(&ctx->streams)) { ngtcp2_conn_set_keep_alive_timeout(ctx->qconn, UINT64_MAX); CURL_TRC_CF(data, cf, "no active streams, unset keep-alive"); } @@ -1900,12 +1900,12 @@ CURLcode Curl_cf_ngtcp2_h3_stream_setup(struct Curl_cfilter *cf, stream->window_size_max = H3_STREAM_WINDOW_SIZE_INITIAL; Curl_h1_req_parse_init(&stream->h1, H1_PARSE_DEFAULT_MAX_LINE_LEN); - if(!Curl_uint32_hash_set(&ctx->streams, data->mid, stream)) { + if(Curl_u32_ptrset_set(&ctx->streams, data->mid, stream)) { Curl_cf_ngtcp2_h3_stream_ctx_free(stream); return CURLE_OUT_OF_MEMORY; } - if(Curl_uint32_hash_count(&ctx->streams) == 1) + if(Curl_u32_ptrset_count(&ctx->streams) == 1) cf_ngtcp2_setup_keep_alive(cf, data); return CURLE_OK; @@ -1942,8 +1942,8 @@ void Curl_cf_ngtcp2_h3_stream_done(struct Curl_cfilter *cf, if(stream) { CURL_TRC_CF(data, cf, "[%" PRId64 "] easy handle is done", stream->id); Curl_cf_ngtcp2_h3_stream_close(cf, data, stream); - Curl_uint32_hash_remove(&ctx->streams, data->mid); - if(!Curl_uint32_hash_count(&ctx->streams)) + Curl_u32_ptrset_unset(&ctx->streams, data->mid); + if(!Curl_u32_ptrset_count(&ctx->streams)) cf_ngtcp2_setup_keep_alive(cf, data); } } diff --git a/lib/vquic/cf-ngtcp2-cmn.h b/lib/vquic/cf-ngtcp2-cmn.h index 530603860f60..0c8b26a255a3 100644 --- a/lib/vquic/cf-ngtcp2-cmn.h +++ b/lib/vquic/cf-ngtcp2-cmn.h @@ -58,7 +58,7 @@ #include #include "http1.h" -#include "uint-hash.h" +#include "u32_ptrset.h" #include "vtls/vtls.h" #include "vquic/vquic_int.h" #include "vquic/vquic-tls.h" @@ -128,7 +128,7 @@ struct cf_ngtcp2_ctx { struct curltime handshake_at; /* time connect handshake finished */ struct bufc_pool stream_bufcp; /* chunk pool for streams */ struct dynbuf scratch; /* temp buffer for header construction */ - struct uint_hash streams; /* hash data->mid to h3_stream_ctx */ + struct u32_ptrset streams; /* set of data->mid + h3_stream_ctx */ uint64_t used_bidi_streams; /* bidi streams we have opened */ uint64_t max_bidi_streams; /* max bidi streams we can open */ size_t earlydata_max; /* max amount of early data supported by diff --git a/lib/vquic/cf-ngtcp2.c b/lib/vquic/cf-ngtcp2.c index 83698a7f3e33..bd026e24dd47 100644 --- a/lib/vquic/cf-ngtcp2.c +++ b/lib/vquic/cf-ngtcp2.c @@ -27,7 +27,7 @@ #include "urldata.h" #include "url.h" -#include "uint-hash.h" +#include "u32_ptrset.h" #include "curl_trc.h" #include "rand.h" #include "multiif.h" diff --git a/lib/vquic/cf-quiche.c b/lib/vquic/cf-quiche.c index adeea37733e8..441638601f10 100644 --- a/lib/vquic/cf-quiche.c +++ b/lib/vquic/cf-quiche.c @@ -29,7 +29,7 @@ #include #include "bufq.h" -#include "uint-hash.h" +#include "u32_ptrset.h" #include "urldata.h" #include "cfilters.h" #include "cf-socket.h" @@ -86,7 +86,7 @@ struct cf_quiche_ctx { uint8_t scid[QUICHE_MAX_CONN_ID_LEN]; struct curltime started_at; /* time the current attempt started */ struct curltime handshake_at; /* time connect handshake finished */ - struct uint_hash streams; /* hash `data->mid` to `stream_ctx` */ + struct u32_ptrset streams; /* set of `data->mid` + `stream_ctx` */ struct dynbuf h1hdr; /* temp buffer for header construction */ struct bufq writebuf; /* temp buffer for writing bodies */ curl_off_t data_recvd; @@ -107,7 +107,7 @@ static void quiche_debug_log(const char *line, void *argp) } #endif -static void h3_stream_hash_free(unsigned int id, void *stream); +static void h3_stream_set_free(uint32_t id, void *stream); static CURLcode cf_quiche_ctx_init(struct cf_quiche_ctx *ctx, struct Curl_peer *origin, @@ -122,7 +122,7 @@ static CURLcode cf_quiche_ctx_init(struct cf_quiche_ctx *ctx, } #endif curlx_dyn_init(&ctx->h1hdr, CURL_MAX_HTTP_HEADER); - Curl_uint32_hash_init(&ctx->streams, 63, h3_stream_hash_free); + Curl_u32_ptrset_init(&ctx->streams, h3_stream_set_free); Curl_bufq_init2(&ctx->writebuf, H3_STREAM_CHUNK_SIZE, H3_STREAM_RECV_CHUNKS, BUFQ_OPT_SOFT_LIMIT); ctx->data_recvd = 0; @@ -138,7 +138,7 @@ static void cf_quiche_ctx_free(struct cf_quiche_ctx *ctx) Curl_vquic_tls_cleanup(&ctx->tls); Curl_ssl_peer_cleanup(&ctx->ssl_peer); Curl_vquic_ctx_free(&ctx->q); - Curl_uint32_hash_destroy(&ctx->streams); + Curl_u32_ptrset_clear(&ctx->streams); curlx_dyn_free(&ctx->h1hdr); Curl_bufq_free(&ctx->writebuf); } @@ -193,7 +193,7 @@ static void h3_stream_ctx_free(struct h3_stream_ctx *stream) curlx_free(stream); } -static void h3_stream_hash_free(unsigned int id, void *stream) +static void h3_stream_set_free(uint32_t id, void *stream) { (void)id; DEBUGASSERT(stream); @@ -233,7 +233,7 @@ static void cf_quiche_for_all_streams(struct Curl_cfilter *cf, vctx.multi = multi; vctx.cb = do_cb; vctx.user_data = user_data; - Curl_uint32_hash_visit(&ctx->streams, cf_quiche_stream_do, &vctx); + Curl_u32_ptrset_visit(&ctx->streams, cf_quiche_stream_do, &vctx); } static bool cf_quiche_do_resume(struct Curl_cfilter *cf, @@ -278,7 +278,7 @@ static CURLcode h3_data_setup(struct Curl_cfilter *cf, stream->id = -1; Curl_h1_req_parse_init(&stream->h1, H1_PARSE_DEFAULT_MAX_LINE_LEN); - if(!Curl_uint32_hash_set(&ctx->streams, data->mid, stream)) { + if(Curl_u32_ptrset_set(&ctx->streams, data->mid, stream)) { h3_stream_ctx_free(stream); return CURLE_OUT_OF_MEMORY; } @@ -318,7 +318,7 @@ static void h3_data_done(struct Curl_cfilter *cf, struct Curl_easy *data) if(stream) { CURL_TRC_CF(data, cf, "[%" PRIu64 "] easy handle is done", stream->id); cf_quiche_stream_close(cf, data, stream); - Curl_uint32_hash_remove(&ctx->streams, data->mid); + Curl_u32_ptrset_unset(&ctx->streams, data->mid); } } @@ -635,7 +635,7 @@ static CURLcode cf_poll_events(struct Curl_cfilter *cf, dctx.cf = cf; dctx.multi = data->multi; dctx.ev = ev; - Curl_uint32_hash_visit(&ctx->streams, cf_quiche_disp_event, &dctx); + Curl_u32_ptrset_visit(&ctx->streams, cf_quiche_disp_event, &dctx); quiche_h3_event_free(ev); } } diff --git a/lib/vquic/vquic_int.h b/lib/vquic/vquic_int.h index 23e1102eb0d9..39398529357a 100644 --- a/lib/vquic/vquic_int.h +++ b/lib/vquic/vquic_int.h @@ -28,6 +28,7 @@ #ifdef USE_HTTP3 #include "bufq.h" +#include "u32_ptrset.h" #define MAX_UDP_PAYLOAD_SIZE 1452 @@ -78,7 +79,7 @@ struct cf_quic_ctx { }; #define H3_STREAM_CTX(ctx, data) \ - ((data) ? Curl_uint32_hash_get(&(ctx)->streams, (data)->mid) : NULL) + ((data) ? Curl_u32_ptrset_get(&(ctx)->streams, (data)->mid) : NULL) CURLcode Curl_vquic_ctx_init(struct Curl_easy *data, struct cf_quic_ctx *qctx); diff --git a/tests/data/Makefile.am b/tests/data/Makefile.am index f84145246fd9..f04f0f8cc1ec 100644 --- a/tests/data/Makefile.am +++ b/tests/data/Makefile.am @@ -1634,7 +1634,6 @@ TESTCASES = \ test1613 \ test1614 \ test1615 \ - test1616 \ test1617 \ test1618 \ test1619 \ diff --git a/tests/data/test1616 b/tests/data/test1616 deleted file mode 100644 index c0719b091f9f..000000000000 --- a/tests/data/test1616 +++ /dev/null @@ -1,19 +0,0 @@ - - - - -unittest -uint_hash - - - -# Client-side - - -unittest - - -uint_hash create/add/destroy testing, exercising clean functions - - - diff --git a/tests/data/test3211 b/tests/data/test3211 index 9be8eced05d7..4e4271e455fd 100644 --- a/tests/data/test3211 +++ b/tests/data/test3211 @@ -5,6 +5,7 @@ unittest uint_bset u8_strset +u32_ptrset @@ -14,7 +15,7 @@ u8_strset unittest -uint_bset and u8_strset unit tests +uint_bset, u8_strset and u32_ptrset unit tests diff --git a/tests/unit/Makefile.inc b/tests/unit/Makefile.inc index b152bc029ec0..af7672004620 100644 --- a/tests/unit/Makefile.inc +++ b/tests/unit/Makefile.inc @@ -55,7 +55,6 @@ TESTS_C = \ unit1612.c \ unit1614.c \ unit1615.c \ - unit1616.c \ unit1620.c \ unit1625.c \ unit1626.c \ diff --git a/tests/unit/unit1399.c b/tests/unit/unit1399.c index e2d6948329a5..5940975d88c0 100644 --- a/tests/unit/unit1399.c +++ b/tests/unit/unit1399.c @@ -120,7 +120,7 @@ static CURLcode test_unit1399(const char *arg) /* Accumulated times are now 3 seconds: * - 2 after 1st request start - * - 1 aftger 2nd requerst start */ + * - 1 after 2nd requerst start */ expect_timer_seconds(data, 3); UNITTEST_END(t1399_stop(data)) diff --git a/tests/unit/unit1616.c b/tests/unit/unit1616.c deleted file mode 100644 index cd7d0cca54ea..000000000000 --- a/tests/unit/unit1616.c +++ /dev/null @@ -1,85 +0,0 @@ -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) Daniel Stenberg, , et al. - * - * This software is licensed as described in the file COPYING, which - * you should have received as part of this distribution. The terms - * are also available at https://curl.se/docs/copyright.html. - * - * You may opt to use, copy, modify, merge, publish, distribute and/or sell - * copies of the Software, and permit persons to whom the Software is - * furnished to do so, under the terms of the COPYING file. - * - * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY - * KIND, either express or implied. - * - * SPDX-License-Identifier: curl - * - ***************************************************************************/ -#include "unitcheck.h" -#include "uint-hash.h" - -static void t1616_mydtor(uint32_t id, void *elem) -{ - int *ptr = (int *)elem; - (void)id; - curlx_free(ptr); -} - -static CURLcode t1616_setup(struct uint_hash *hash) -{ - Curl_uint32_hash_init(hash, 15, t1616_mydtor); - return CURLE_OK; -} - -static void t1616_stop(struct uint_hash *hash) -{ - Curl_uint32_hash_destroy(hash); -} - -static CURLcode test_unit1616(const char *arg) -{ - struct uint_hash hash; - - UNITTEST_BEGIN(t1616_setup(&hash)) - - int *value, *v; - int *value2; - bool ok; - - uint32_t key = 20; - uint32_t key2 = 25; - - value = curlx_malloc(sizeof(int)); - abort_unless(value, "Out of memory"); - *value = 199; - ok = Curl_uint32_hash_set(&hash, key, value); - if(!ok) - curlx_free(value); - abort_unless(ok, "insertion into hash failed"); - v = Curl_uint32_hash_get(&hash, key); - abort_unless(v == value, "lookup present entry failed"); - v = Curl_uint32_hash_get(&hash, key2); - abort_unless(!v, "lookup missing entry failed"); - uint_hash_clear(&hash); - - /* Attempt to add another key/value pair */ - value2 = curlx_malloc(sizeof(int)); - abort_unless(value2, "Out of memory"); - *value2 = 204; - ok = Curl_uint32_hash_set(&hash, key2, value2); - if(!ok) - curlx_free(value2); - abort_unless(ok, "insertion into hash failed"); - v = Curl_uint32_hash_get(&hash, key2); - abort_unless(v == value2, "lookup present entry failed"); - v = Curl_uint32_hash_get(&hash, key); - abort_unless(!v, "lookup missing entry failed"); - - UNITTEST_END(t1616_stop(&hash)) -} diff --git a/tests/unit/unit3211.c b/tests/unit/unit3211.c index 3f2aee891816..0ec959ebc9e2 100644 --- a/tests/unit/unit3211.c +++ b/tests/unit/unit3211.c @@ -25,6 +25,7 @@ #include "urldata.h" #include "uint-bset.h" #include "u8_strset.h" +#include "u32_ptrset.h" #include "curl_trc.h" static void t3211_check_bset(const char *name, uint32_t capacity, @@ -202,7 +203,7 @@ static void t3211_check_u8_strset(void) fail_unless(!t3211_strcmp(buf, Curl_u8_strset_get(&set, idx)), "wrong get loop5"); } - fail_unless(Curl_u8_strset_count(&set) == 9, "wrong count aftger add 5"); + fail_unless(Curl_u8_strset_count(&set) == 9, "wrong count after add 5"); Curl_u8_strset_clear(&set); @@ -220,6 +221,62 @@ static void t3211_check_u8_strset(void) fail_unless(!Curl_u8_strset_count(&set), "cleared strset not empty"); } +static void t3211_check_u32_ptrset(void) +{ + struct u32_ptrset set; + CURLcode result; + uint32_t i, idx; + + Curl_u32_ptrset_init(&set, NULL); + fail_unless(!Curl_u32_ptrset_count(&set), "initial ptrset not empty"); + + result = Curl_u32_ptrset_set(&set, 0, CURL_UNCONST("123")); + fail_unless(!result, "ptrset add1 failed"); + fail_unless(Curl_u32_ptrset_get(&set, 0), "ptrset get failed"); + fail_unless(!t3211_strcmp("123", Curl_u32_ptrset_get(&set, 0)), + "ptrset wrong get1"); + result = Curl_u32_ptrset_set(&set, 0, CURL_UNCONST("456")); + fail_unless(!result, "add2 failed"); + fail_unless(!t3211_strcmp("456", Curl_u32_ptrset_get(&set, 0)), + "ptrset wrong get2"); + Curl_u32_ptrset_unset(&set, 0); + fail_unless(!Curl_u32_ptrset_get(&set, 0), "ptrset unset failed"); + + /* Initial size is 64, add 64 hash collisions */ + for(i = 0; i < 64; ++i) { + idx = (uint32_t)((64 * i) + 3); + result = Curl_u32_ptrset_set(&set, idx, CURL_UNCONST("loop64")); + fail_unless(!result, "ptrset loop64 add failed"); + fail_unless(!t3211_strcmp("loop64", Curl_u32_ptrset_get(&set, idx)), + "ptrset wrong get loop64"); + } + + /* Remove collided entry 2, check again */ + idx = (uint32_t)((64 * 2) + 3); + Curl_u32_ptrset_unset(&set, idx); + fail_unless(!Curl_u32_ptrset_get(&set, idx), "ptrset unset2 failed"); + + /* Add entry 2 again, check */ + idx = (uint8_t)((64 * 2) + 3); + result = Curl_u32_ptrset_set(&set, idx, CURL_UNCONST("loop64b")); + fail_unless(!result, "ptrset re-add 2 failed"); + fail_unless(!t3211_strcmp("loop64b", Curl_u32_ptrset_get(&set, idx)), + "ptrset wrong re-add 2 get"); + + /* Add a 65th, set grows */ + fail_unless(Curl_u32_ptrset_count(&set) == 64, + "ptrset wrong count pre add 5"); + idx = (uint32_t)((65 * 4) + 3); + result = Curl_u32_ptrset_set(&set, idx, CURL_UNCONST("add65")); + fail_unless(!result, "ptrset add65 failed"); + fail_unless(!t3211_strcmp("add65", Curl_u32_ptrset_get(&set, idx)), + "ptrset wrong result after add65"); + fail_unless(Curl_u32_ptrset_count(&set) == 65, "wrong count after add65"); + + Curl_u32_ptrset_clear(&set); + fail_unless(!Curl_u32_ptrset_count(&set), "ptrset cleared not empty"); +} + static CURLcode test_unit3211(const char *arg) { UNITTEST_BEGIN_SIMPLE @@ -244,6 +301,7 @@ static CURLcode test_unit3211(const char *arg) t3211_check_bset("s2", 1000, s2, CURL_ARRAYSIZE(s2)); t3211_check_u8_strset(); + t3211_check_u32_ptrset(); UNITTEST_END_SIMPLE } From 0480ce153276b4a6f029ce5890fc0c493c07e8c3 Mon Sep 17 00:00:00 2001 From: Daniel Stenberg Date: Thu, 17 Sep 2026 23:58:16 +0200 Subject: [PATCH 165/481] perf-numparser: add number parsing performance test Closes #22990 --- tests/perf/Makefile.inc | 1 + tests/perf/README.md | 8 ++ tests/perf/numparser.c | 239 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 248 insertions(+) create mode 100644 tests/perf/numparser.c diff --git a/tests/perf/Makefile.inc b/tests/perf/Makefile.inc index 456fbae2f129..93fe64030d95 100644 --- a/tests/perf/Makefile.inc +++ b/tests/perf/Makefile.inc @@ -47,6 +47,7 @@ TESTS_C = \ base64enc.c \ dateparser.c \ maprintf.c \ + numparser.c \ simpleformat.c \ snprintf.c \ urldecode.c \ diff --git a/tests/perf/README.md b/tests/perf/README.md index ebec32e4e901..97af37ceaec7 100644 --- a/tests/perf/README.md +++ b/tests/perf/README.md @@ -46,6 +46,14 @@ test does not: ./perf maprintf [loops] +## `numparser` + +This test repeatedly converts over a hundred different numbers from a string +to binary. Decimal, hexadecimal and octal numbers, each with a set cap. Many +numbers hit the cap, some numbers are not syntactically correct. + + ./perf numparser [loops] + ## `simpleformat` This test repeatedly formats a commonly used simple message with diff --git a/tests/perf/numparser.c b/tests/perf/numparser.c new file mode 100644 index 000000000000..da3ae34e0b63 --- /dev/null +++ b/tests/perf/numparser.c @@ -0,0 +1,239 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ +#include "first.h" + +struct num { + const char *str; + int base; + curl_off_t max; +}; + +static const struct num nums[] = { + { "9223372036854775807", 10, 9223372036854775807LL }, + { "123456789123456123A", 10, 9223372036854775807LL }, + { "123456789123456123B", 10, 9223372036854775807LL }, + { "123456789123456123C", 10, 9223372036854775807LL }, + { "123456789123456123D", 10, 9223372036854775807LL }, + { "1234567891234561234", 10, 9223372036854775807LL }, + { "123456789123456123", 10, 9223372036854775807LL }, + { "12345678912345612", 10, 9223372036854775807LL }, + { "1234567891234561", 10, 9223372036854775807LL }, + { "123456789123456", 10, 9223372036854775807LL }, + { "12345678912345", 10, 9223372036854775807LL }, + { "1234567891234", 10, 9223372036854775807LL }, + { "123456789123", 10, 9223372036854775807LL }, + { "12345678912", 10, 9223372036854775807LL }, + { "1234567891", 10, 9223372036854775807LL }, + { "123456789", 10, 9223372036854775807LL }, + { "12345678", 10, 9223372036854775807LL }, + { "1234567", 10, 9223372036854775807LL }, + { "123456", 10, 9223372036854775807LL }, + { "12345", 10, 9223372036854775807LL }, + { "1234", 10, 9223372036854775807LL }, + { "123", 10, 9223372036854775807LL }, + { "12", 10, 9223372036854775807LL }, + { "1", 10, 9223372036854775807 }, + { "9223372036854775807", 10, 9223372036854775806LL }, + { "1234567891234561234", 10, 1234567891234561233LL }, + { "234567891234561234", 10, 234567891234561233LL }, + { "34567891234561234", 10, 34567891234561233LL }, + { "4567891234561234", 10, 4567891234561233LL }, + { "567891234561234", 10, 567891234561233LL }, + { "67891234561234", 10, 67891234561233LL }, + { "7891234561234", 10, 7891234561233LL }, + { "891234561234", 10, 891234561233LL }, + { "91234561234", 10, 91234561233LL }, + { "1234561234", 10, 1234561233LL }, + { "234561234", 10, 234561233LL }, + { "34561234", 10, 34561233LL }, + { "4561234", 10, 4561233LL }, + + { "7FFFFFFFFFFFFFFF", 16, 0x7FFFFFFFFFFFFFFFLL }, + { "7FFFFFFFFFFFFFFF", 16, 0x7FFFFFFFFFFFFFFELL }, + { "7FFFFFFFFFFFFFFF", 16, 0x7FFFFFFFFFFFFFFDLL }, + { "7FFFFFFFFFFFFFFF", 16, 0x7FFFFFFFFFFFFFFCLL }, + { "7FFFFFFFFFFFFFFF", 16, 0x7FFFFFFFFFFFFFFBLL }, + { "7FFFFFFFFFFFFFFE", 16, 0x7FFFFFFFFFFFFFFFLL }, + { "7FFFFFFFFFFFFFFD", 16, 0x7FFFFFFFFFFFFFFFLL }, + { "7FFFFFFFFFFFFFFC", 16, 0x7FFFFFFFFFFFFFFFLL }, + { "7FFFFFFFFFFFFFFB", 16, 0x7FFFFFFFFFFFFFFFLL }, + { "7FFFFFFFFFFFFFFA", 16, 0x7FFFFFFFFFFFFFFFLL }, + { "7FFFFFFFFFFFFFF9", 16, 0x7FFFFFFFFFFFFFFFLL }, + { "7FFFFFFFFFFFFFFG", 16, 0x7FFFFFFFFFFFFFFFLL }, + { "GFFFFFFFFFFFFFF9", 16, 0x7FFFFFFFFFFFFFFFLL }, + { "HFFFFFFFFFFFFFF9", 16, 0x7FFFFFFFFFFFFFFFLL }, + { "IFFFFFFFFFFFFFF9", 16, 0x7FFFFFFFFFFFFFFFLL }, + { "KFFFFFFFFFFFFFF9", 16, 0x7FFFFFFFFFFFFFFFLL }, + { "1234567890123456", 16, 0x1234567890123456LL }, + { "123456789012345G", 16, 0x1234567890123456LL }, + { "123456789012345H", 16, 0x1234567890123456LL }, + { "123456789012345I", 16, 0x1234567890123456LL }, + { "123456789012345J", 16, 0x1234567890123456LL }, + { "123456789012345K", 16, 0x1234567890123456LL }, + { "234567890123456", 16, 0x1234567890123456LL }, + { "34567890123456", 16, 0x1234567890123456LL }, + { "4567890123456", 16, 0x1234567890123456LL }, + { "567890123456", 16, 0x1234567890123456LL }, + { "567890123456", 16, 0x1234567890123456LL }, + { "67890123456", 16, 0x1234567890123456LL }, + { "7890123456", 16, 0x1234567890123456LL }, + { "890123456", 16, 0x1234567890123456LL }, + { "90123456", 16, 0x1234567890123456LL }, + { "0123456", 16, 0x1234567890123456LL }, + { "123456", 16, 0x1234567890123456LL }, + { "23456", 16, 0x1234567890123456LL }, + { "3456", 16, 0x1234567890123456LL }, + { "456", 16, 0x1234567890123456LL }, + { "56", 16, 0x1234567890123456LL }, + { "6", 16, 0x1234567890123456LL }, + + { "1234567890123456", 16, 0x1234567890123455LL }, + { "234567890123456", 16, 0x234567890123455LL }, + { "34567890123456", 16, 0x34567890123455LL }, + { "4567890123456", 16, 0x4567890123455LL }, + { "67890123456", 16, 0x67890123455LL }, + { "7890123456", 16, 0x7890123455LL }, + { "890123456", 16, 0x890123455LL }, + { "90123456", 16, 0x90123455LL }, + { "0123456", 16, 0x0123455LL }, + { "123456", 16, 0x123455LL }, + { "23456", 16, 0x23455LL }, + + { "10", 10, 5 }, + { "9", 10, 5 }, + { "8", 10, 5 }, + { "7", 10, 5 }, + { "3", 10, 5 }, + { "2", 10, 5 }, + { "1", 10, 5 }, + + { "10", 16, 8 }, + { "9", 16, 8 }, + { "8", 16, 8 }, + { "7", 16, 6 }, + { "3", 16, 7 }, + { "2", 16, 5 }, + { "1", 16, 5 }, + + {"777777777777777777777", 8, 0777777777777777777777LL }, + {"777777777777777777776", 8, 0777777777777777777777LL }, + {"777777777777777777775", 8, 0777777777777777777777LL }, + {"777777777777777777774", 8, 0777777777777777777777LL }, + {"777777777777777777773", 8, 0777777777777777777777LL }, + {"777777777777777777772", 8, 0777777777777777777777LL }, + {"777777777777777777779", 8, 0777777777777777777777LL }, + {"123456701234567012345", 8, 0123456701234567012345LL }, + {"12345670123456701234", 8, 0123456701234567012345LL }, + {"1234567012345670123", 8, 0123456701234567012345LL }, + {"123456701234567012", 8, 0123456701234567012345LL }, + {"12345670123456701", 8, 0123456701234567012345LL }, + {"1234567012345670", 8, 0123456701234567012345LL }, + {"123456701234567", 8, 0123456701234567012345LL }, + {"12345670123456", 8, 0123456701234567012345LL }, + {"1234567012345", 8, 0123456701234567012345LL }, + {"123456701234", 8, 0123456701234567012345LL }, + {"12345670123", 8, 0123456701234567012345LL }, + {"1234567012", 8, 0123456701234567012345LL }, + {"123456701", 8, 0123456701234567012345LL }, + + {"123456701234567012345", 8, 0123456701234567012344LL }, + {"23456701234567012345", 8, 023456701234567012344LL }, + {"3456701234567012345", 8, 03456701234567012344LL }, + {"456701234567012345", 8, 0456701234567012344LL }, + {"56701234567012345", 8, 056701234567012344LL }, + {"6701234567012345", 8, 06701234567012344LL }, + {"701234567012345", 8, 0701234567012344LL }, + {"01234567012345", 8, 01234567012344LL }, + {"1234567012345", 8, 01234567012344LL }, + {"234567012345", 8, 0234567012344LL }, + {"34567012345", 8, 034567012344LL }, + {"4567012345", 8, 04567012344LL }, + {"567012345", 8, 0567012344LL }, + {"67012345", 8, 067012344LL }, + + {"123456701234567012348", 8, 0123456701234567012344LL }, + {"123456701234567012349", 8, 0123456701234567012344LL }, + {"12345670123456701234A", 8, 0123456701234567012344LL }, + {"12345670123456701234C", 8, 0123456701234567012344LL }, + {"12345670123456701234B", 8, 0123456701234567012344LL }, + +}; + +/* + * Parse the numbers + */ +static int test_numparser(int argc, const char **argv) +{ + struct curltime start; + struct curltime end; + timediff_t us; + long long hn; + curl_off_t loops = 500000, loop; + curl_off_t count = 0; + curl_off_t ecount = 0; /* errors */ + size_t i; + size_t nnumbers = CURL_ARRAYSIZE(nums); + + if(argc > 1) { + const char *ptr = argv[1]; + curl_off_t num; + if(!curlx_str_number(&ptr, &num, INT_MAX) && !*ptr) + loops = num; + } + + curl_mprintf("Found %zu numbers to parse\n", nnumbers); + + start = curlx_now(); + for(loop = 0; loop < loops; loop++) { + for(i = 0; i < nnumbers; i++) { + const char *p = nums[i].str; + curl_off_t val; + int err; + if(nums[i].base == 10) + err = curlx_str_number(&p, &val, nums[i].max); + else if(nums[i].base == 16) + err = curlx_str_hex(&p, &val, nums[i].max); + else + err = curlx_str_octal(&p, &val, nums[i].max); + if(err) + ecount++; + count++; + } + } + end = curlx_now(); + us = curlx_timediff_us(end, start); /* how many microseconds */ + hn = count ? us * 1000000 / count : 0; /* 1000 times too big */ + curl_mprintf("Numbers: %" CURL_FORMAT_CURL_OFF_T "\n" + "Time: %lld usecs\n" + "Time/num: %lld.%03lld ns\n" + "Nums/sec: %lld\n" + "Errors: %" CURL_FORMAT_CURL_OFF_T "\n", + count, + (long long)us, + (long long)hn / 1000, + (long long)hn % 1000, + us ? (long long)(count * 1000000) / us : 0, + ecount); + return 0; +} From 029611f31bbf4aeeb5d48926d61fbd71a6936cf5 Mon Sep 17 00:00:00 2001 From: Daniel Stenberg Date: Mon, 14 Sep 2026 14:49:25 +0200 Subject: [PATCH 166/481] rtspd: read the test number from the server.cmd file In the same style sws aleady does it. Removes the need to have the test number in the request. Closes #22989 --- tests/server/first.h | 2 ++ tests/server/rtspd.c | 25 +++++++++++++++++++++++-- tests/server/sws.c | 2 +- 3 files changed, 26 insertions(+), 3 deletions(-) diff --git a/tests/server/first.h b/tests/server/first.h index ff8851f9cdff..bec2210a58eb 100644 --- a/tests/server/first.h +++ b/tests/server/first.h @@ -181,6 +181,8 @@ static const char *server_unix_socket = NULL; static uint16_t server_port = 0; static const char *socket_type = "IPv4"; static int socket_domain = AF_INET; +/* file in which additional instructions may be found */ +extern const char *cmdfile; #define SERVERLOGS_LOCKDIR "lock" /* within logdir */ diff --git a/tests/server/rtspd.c b/tests/server/rtspd.c index 44e4f68655a4..59db53cbd50e 100644 --- a/tests/server/rtspd.c +++ b/tests/server/rtspd.c @@ -141,6 +141,24 @@ static const char doc404_RTSP[] = "RTSP/1.0 404 Not Found\r\n" #define RTP_DATA_SIZE 12 static const char RTP_DATA[] = "$_1234\n\0Rsdf"; +/* parse the file on disk that might have a test number for us */ +static int rtspd_cmdfile(struct rtspd_httprequest *req) +{ + FILE *f = curlx_fopen(cmdfile, FOPEN_READTEXT); + if(f) { + int testnum = DOCNUMBER_NOTHING; + char buf[256]; + while(fgets(buf, sizeof(buf), f)) { + if(sscanf(buf, "Testnum %d", &testnum) == 1) { + logmsg("[%s] cmdfile says testnum %d", cmdfile, testnum); + req->testno = testnum; + } + } + curlx_fclose(f); + } + return 0; +} + static int rtspd_ProcessRequest(struct rtspd_httprequest *req) { const char *line = &req->reqbuf[req->checkindex]; @@ -388,8 +406,11 @@ static int rtspd_ProcessRequest(struct rtspd_httprequest *req) else req->testno = DOCNUMBER_CONNECT; } - else { - logmsg("Did not find test number in PATH"); + if(req->testno == DOCNUMBER_NOTHING) + /* might get the test number */ + rtspd_cmdfile(req); + if(req->testno == DOCNUMBER_NOTHING) { + logmsg("Did not find test number"); req->testno = DOCNUMBER_404; } } diff --git a/tests/server/sws.c b/tests/server/sws.c index 5d7087927d9b..5587e50feaf7 100644 --- a/tests/server/sws.c +++ b/tests/server/sws.c @@ -104,7 +104,7 @@ static size_t num_sockets = 0; (is_proxy ? REQUEST_PROXY_DUMP : REQUEST_DUMP) /* file in which additional instructions may be found */ -static const char *cmdfile = "log/server.cmd"; +const char *cmdfile = "log/server.cmd"; /* very-big-path support */ #define MAXDOCNAMELEN 140000 From c0609c5a4f9a0bf12af855bc3711717375c45f5e Mon Sep 17 00:00:00 2001 From: Daniel Stenberg Date: Mon, 14 Sep 2026 15:49:04 +0200 Subject: [PATCH 167/481] http: abide to close-rules when transfer-encoding off There is no other way to know when the transfer ends when there is a transfer encoding set but not chunked - even if transfere-encoding is disabled. Tweak test 319 to verify this (by setting a wrong Content-Length). Add test 2918 to make sure it works the same also when the Content-Length and Transfer-Encoding headers arrive in the other order Fixed test 1494, it needs to close after "Transfer-Encoding: identity". Assisted-by: Stefan Eissing Reported-by: Richard Payne Closes #22950 --- lib/http.c | 4 +-- tests/data/Makefile.am | 1 + tests/data/test1494 | 2 +- tests/data/test2918 | 55 ++++++++++++++++++++++++++++++++++++++++++ tests/data/test319 | 18 ++++++-------- 5 files changed, 66 insertions(+), 14 deletions(-) create mode 100644 tests/data/test2918 diff --git a/lib/http.c b/lib/http.c index 3894f53c0fbc..0aba9f2002d6 100644 --- a/lib/http.c +++ b/lib/http.c @@ -3248,7 +3248,7 @@ static CURLcode http_header_c(struct Curl_easy *data, list also is fine and then we should accept them all as long as they are the same value. Different values trigger error. */ - v = (!k->http_bodyless && !data->set.ignorecl) ? + v = (!k->http_bodyless && !data->set.ignorecl && !k->ignore_cl) ? HD_VAL(hd, hdlen, "Content-Length:") : NULL; if(v) { do { @@ -3621,7 +3621,7 @@ static CURLcode http_header_t(struct Curl_easy *data, CURLcode result = Curl_build_unencoding_stack(data, v, TRUE); if(result) return result; - if(!k->chunk && data->set.http_transfer_encoding) { + if(!k->chunk) { /* if this is not chunked, only close can signal the end of this * transfer as Content-Length is said not to be trusted for * transfer-encoding! */ diff --git a/tests/data/Makefile.am b/tests/data/Makefile.am index f04f0f8cc1ec..86cd82464e7c 100644 --- a/tests/data/Makefile.am +++ b/tests/data/Makefile.am @@ -2014,6 +2014,7 @@ TESTCASES = \ test2723 \ test2805 \ test2885 \ + test2918 \ test2924 \ test3000 \ test3001 \ diff --git a/tests/data/test1494 b/tests/data/test1494 index cd661eb38093..4a7e25071382 100644 --- a/tests/data/test1494 +++ b/tests/data/test1494 @@ -11,7 +11,7 @@ DELAY # Server-side -HTTP/1.1 200 funky chunky! +HTTP/1.1 200 funky chunky! swsclose Server: fakeit/0.9 fakeitbad/1.0 Transfer-Encoding: identity, identity Content-Length: 19 diff --git a/tests/data/test2918 b/tests/data/test2918 new file mode 100644 index 000000000000..e166d5dd898f --- /dev/null +++ b/tests/data/test2918 @@ -0,0 +1,55 @@ + + + + +HTTP +HTTP GET +Transfer-Encoding +--raw + + +# Server-side + + +# Similar to test 319 but receives headers in a different order + +HTTP/1.1 200 OK swsclose +Date: Mon, 29 Nov 2004 21:56:53 GMT +Server: Apache/1.3.31 (Debian GNU/Linux) mod_gzip/1.3.26.1a PHP/4.3.9-1 mod_ssl/2.8.20 OpenSSL/0.9.7d mod_perl/1.29 +Content-Type: text/html; charset=ISO-8859-1 +Content-Length: 4400 +Transfer-Encoding: gobbledigook + +This is data +that is encoded +but the client +does not know +how to decode + + + + +# Client-side + + +http + + +Ignore content-length with Transfer-Encoding + + +http://%HOSTIP:%HTTPPORT/%TESTNUMBER --raw + + + +# Verify data after the test has been "shot" + + +GET /%TESTNUMBER HTTP/1.1 +Host: %HOSTIP:%HTTPPORT +User-Agent: curl/%VERSION +Accept: */* + + + + diff --git a/tests/data/test319 b/tests/data/test319 index 33bb2586cb4f..e55dfa21f22a 100644 --- a/tests/data/test319 +++ b/tests/data/test319 @@ -16,19 +16,15 @@ Date: Mon, 29 Nov 2004 21:56:53 GMT Server: Apache/1.3.31 (Debian GNU/Linux) mod_gzip/1.3.26.1a PHP/4.3.9-1 mod_ssl/2.8.20 OpenSSL/0.9.7d mod_perl/1.29 Content-Type: text/html; charset=ISO-8859-1 Transfer-Encoding: gobbledigook -Content-Length: 44 +Content-Length: 4400 -%hex[%1f%8b%08%08%79%9e%ab%41%00%03%6c%61%6c%61%6c%61%00%cb%c9%cc%4b%55%30%e4%52%c8%01%d1%46%5c]hex% -%hex[%10%86%31%17%00]hex% -%hex[%02%71%60%18%00%00%00]hex% +This is data +that is encoded +but the client +does not know +how to decode - -%hex[%1f%8b%08%08%79%9e%ab%41%00%03%6c%61%6c%61%6c%61%00%cb%c9%cc%4b%55%30%e4%52%c8%01%d1%46%5c]hex% -%hex[%10%86%31%17%00]hex% -%hex[%02%71%60%18%00%00%00]hex% - - # Client-side @@ -39,7 +35,7 @@ http HTTP GET gobbledigook transfer-encoded data in raw mode - + http://%HOSTIP:%HTTPPORT/%TESTNUMBER --raw From 8e20dbc62e2107f32e137f9295db99d183a0bf3c Mon Sep 17 00:00:00 2001 From: Stefan Eissing Date: Fri, 18 Sep 2026 10:17:22 +0200 Subject: [PATCH 168/481] u8_strset, u32_ptrset: fix boundaries handling - u8_strset: iterator over all entries using a uint16_t index - u32_ptrset: limit max size to 31 bits, managing INT32_MAX entries Closes #22991 --- lib/u32_ptrset.c | 8 +++++--- lib/u32_ptrset.h | 2 +- lib/u8_strset.c | 11 +++++++---- 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/lib/u32_ptrset.c b/lib/u32_ptrset.c index fe0f685d8f0b..bc0f74c41f59 100644 --- a/lib/u32_ptrset.c +++ b/lib/u32_ptrset.c @@ -117,18 +117,20 @@ static bool u32_ptrset_grow(struct u32_ptrset *set) uint8_t nslotbits, pslotbits; void **prev_data; size_t nslots; + const size_t slot_size = (sizeof(void *) + (2 * sizeof(uint32_t))); void *d; - if(set->slotbits >= 32) + if(set->slotbits >= 31) return FALSE; nslotbits = set->slotbits ? (uint8_t)(set->slotbits + 1) : 5; nslots = CURL_U32_SLOT_CNT(nslotbits); + if(nslots > (SIZE_MAX / slot_size)) /* 32-bit arch may trigger here */ + return FALSE; #if CURL_U32_PTRSET_DEBUG curl_mfprintf(stderr, "u32_ptrset_grow from bits=%d to %d, slots=%zu\n", set->slotbits, nslotbits, nslots); #endif - d = curlx_calloc(1, (nslots * sizeof(void *)) + - (2 * nslots * sizeof(uint32_t))); + d = curlx_calloc(1, nslots * slot_size); if(!d) return FALSE; diff --git a/lib/u32_ptrset.h b/lib/u32_ptrset.h index 6d6868660f55..6ce9a1190963 100644 --- a/lib/u32_ptrset.h +++ b/lib/u32_ptrset.h @@ -27,7 +27,7 @@ typedef void Curl_u32_ptrset_dtor(uint32_t id, void *ptr); -/* A set that can hold up UINT32_MAX pointers identified by an `id'. +/* A set that can hold up to INT32_MAX+1 pointers identified by an `id'. * Setting a pointer for an existing id replaces the previous one. * Getting the pointer for an id not in the set returns NULL. * Setting an id to NULL unsets the id. diff --git a/lib/u8_strset.c b/lib/u8_strset.c index be1148c4442e..e2e368e01a1c 100644 --- a/lib/u8_strset.c +++ b/lib/u8_strset.c @@ -117,7 +117,7 @@ static void u8_strset_addn(struct u8_strset *set, uint8_t id, char *val) static bool u8_strset_grow(struct u8_strset *set) { - uint8_t i, *prev_ids, nslotbits; + uint8_t *prev_ids, nslotbits; uint16_t prev_slots; char **prev_data; size_t nslots; @@ -152,9 +152,12 @@ static bool u8_strset_grow(struct u8_strset *set) set->slotmask = CURL_U8_SLOT_MASK(set->slotbits); set->count = 0; /* re-add previous entries */ - for(i = 0; i < CURL_U8_SLOT_CNT(prev_slots); ++i) { - if(prev_data[i]) - u8_strset_addn(set, prev_ids[i], prev_data[i]); + { + uint16_t i; + for(i = 0; i < CURL_U8_SLOT_CNT(prev_slots); ++i) { + if(prev_data[i]) + u8_strset_addn(set, prev_ids[i], prev_data[i]); + } } if(prev_data != set->sdata) curlx_free(prev_data); From d1823ebc17e5778c30f8a6b2221a057ec607a9b7 Mon Sep 17 00:00:00 2001 From: Stefan Eissing Date: Tue, 15 Sep 2026 14:46:40 +0200 Subject: [PATCH 169/481] connectionpool: some tweaks - check limits when adding a connection to the pool, using one lock - when finding an existing connection, define results on match callback that can trigger a connection close (cheaper) and avoid bundle re-lookups. - perform pruning of dead connection as part of find op, using one lock Closes #22956 --- lib/cf-https-connect.c | 2 +- lib/conncache.c | 333 ++++++++++++++++++++++++----------------- lib/conncache.h | 66 ++++---- lib/cshutdn.c | 54 ++++--- lib/cshutdn.h | 11 +- lib/http.c | 2 +- lib/multi.c | 48 +++--- lib/progress.c | 4 +- lib/progress.h | 2 +- lib/tftp.c | 2 +- lib/url.c | 175 ++++++++++------------ lib/vquic/vquic.c | 10 +- lib/vquic/vquic.h | 3 +- lib/vssh/libssh.c | 2 +- lib/vssh/libssh2.c | 2 +- tests/data/test1554 | 6 - 16 files changed, 365 insertions(+), 357 deletions(-) diff --git a/lib/cf-https-connect.c b/lib/cf-https-connect.c index 8ede52621479..5c8a7a92029c 100644 --- a/lib/cf-https-connect.c +++ b/lib/cf-https-connect.c @@ -276,7 +276,7 @@ static bool cf_hc_may_h3(struct Curl_cfilter *cf, struct cf_hc_ctx *ctx = cf->ctx; if(!ctx->checked_h3) { ctx->check_h3_result = - Curl_conn_may_http3(data, cf->conn, ctx->def_transport); + Curl_conn_may_http3(data, cf->conn); ctx->checked_h3 = TRUE; } return !ctx->check_h3_result; diff --git a/lib/conncache.c b/lib/conncache.c index eb0a7638cd45..00ee24263fb9 100644 --- a/lib/conncache.c +++ b/lib/conncache.c @@ -449,111 +449,109 @@ static void cpool_evict_conn(struct cpool *cpool, cpool_conn_close(cpool, admin, conn, FALSE); } -int Curl_cpool_check_limits(struct Curl_easy *data, - struct connectdata *conn, - const struct curltime *pnow) +#define CPOOL_LIMIT_OK 0 +#define CPOOL_LIMIT_DEST 1 +#define CPOOL_LIMIT_TOTAL 2 + +static int cpool_check_limits(struct Curl_easy *data, + uint32_t max_total, + struct connectdata *to_add, + uint32_t max_host, + struct cpool_bundle **pbundle, + const struct curltime *pnow) { struct cpool *cpool = cpool_get_instance(data); - struct Curl_multi *multi = data->multi; - struct Curl_easy *admin; - struct cpool_bundle *bundle; - size_t dest_limit = 0; - size_t total_limit = 0; - size_t shutdowns; - int res = CPOOL_LIMIT_OK; + struct cshutdn *cshutdn = data->multi ? &data->multi->cshutdn : NULL; + struct Curl_easy *admin = NULL; if(!cpool) return CPOOL_LIMIT_OK; - /* multi determines the limits, no matter who owns the pool */ - if(multi) { - dest_limit = multi->max_host_connections; - total_limit = multi->max_total_connections; - } - - if(!dest_limit && !total_limit) + if(!max_total && !max_host) return CPOOL_LIMIT_OK; - admin = Curl_get_admin(data); - CPOOL_LOCK(cpool, admin); - if(dest_limit) { - size_t live; - - bundle = cpool_find_bundle(cpool, conn->destination); - live = bundle ? Curl_llist_count(&bundle->conns) : 0; - shutdowns = - multi ? Curl_cshutdn_dest_count(&multi->cshutdn, conn->destination) : 0; - while((live + shutdowns) >= dest_limit) { - if(shutdowns) { - /* close one connection in shutdown right away, if we can */ - if(!Curl_cshutdn_close_oldest(&multi->cshutdn, multi->admin, - conn->destination)) - break; - } - else if(!bundle) - break; - else { - struct connectdata *oldest_idle = NULL; - /* The bundle is full. Extract the oldest connection that may - * be removed now, if there is one. */ - oldest_idle = cpool_bundle_get_oldest_idle(bundle, pnow); - if(!oldest_idle) - break; - /* disconnect the old conn and continue */ - CURL_TRC_M(admin, "Discarding connection #%" FMT_OFF_T - " from %zu to reach destination limit of %zu", - oldest_idle->connection_id, - Curl_llist_count(&bundle->conns), dest_limit); - cpool_evict_conn(cpool, admin, oldest_idle); - - /* in case the bundle was destroyed in disconnect, look it up again */ - bundle = cpool_find_bundle(cpool, conn->destination); - live = bundle ? Curl_llist_count(&bundle->conns) : 0; + if(max_host) { + size_t live = 0, shutdowns = 0; + /* if we are at or above `dest_limit`, try to get rid of connections + * in shutdown and, if that does not lower it, evict idle connections + * from the pool. */ + admin = Curl_get_admin(data); + if(*pbundle) { + live = Curl_llist_count(&(*pbundle)->conns); + if(live >= max_host) { + size_t over = live - max_host + 1; + for(; over && *pbundle; --over) { + struct connectdata *oldest_idle = + cpool_bundle_get_oldest_idle(*pbundle, pnow); + if(!oldest_idle) + break; + /* disconnect the old conn and continue */ + CURL_TRC_M(admin, "Shutting down connection #%" FMT_OFF_T + " to '%s' due to destination limit of %u", + oldest_idle->connection_id, oldest_idle->destination, + max_host); + cpool_evict_conn(cpool, admin, oldest_idle); + /* bundle may get destroyed in disconnect, look it up again */ + *pbundle = cpool_find_bundle(cpool, to_add->destination); + } + live = *pbundle ? Curl_llist_count(&(*pbundle)->conns) : 0; } - shutdowns = multi ? - Curl_cshutdn_dest_count(&multi->cshutdn, conn->destination) : 0; } - if((live + shutdowns) >= dest_limit) { - res = CPOOL_LIMIT_DEST; - goto out; + if(cshutdn) { + shutdowns = Curl_cshutdn_dest_count(cshutdn, to_add->destination); + if(shutdowns && ((live + shutdowns) >= max_host)) { + size_t over = live + shutdowns - max_host + 1; + shutdowns -= Curl_cshutdn_close_oldest(cshutdn, admin, + to_add->destination, over); + } } + /* We may not have gotten rid of as many connections as we wanted to */ + if((live + shutdowns) >= max_host) + return CPOOL_LIMIT_DEST; } - if(total_limit) { - shutdowns = multi ? Curl_cshutdn_count(&multi->cshutdn) : 0; - while((cpool->num_conn + shutdowns) >= total_limit) { - if(shutdowns) { - /* close one connection in shutdown right away, if we can */ - if(!Curl_cshutdn_close_oldest(&multi->cshutdn, multi->admin, NULL)) - break; - } - else { + /* Internal transfers are outside the total limit */ + if(max_total && !data->state.internal) { + size_t shutdowns = 0; + + if(!admin) + admin = Curl_get_admin(data); + if(cpool->num_conn >= max_total) { + size_t over = cpool->num_conn - max_total + 1; + for(; over; --over) { struct connectdata *oldest_idle = cpool_get_oldest_idle(cpool, pnow, 0); if(!oldest_idle) break; /* disconnect the old conn and continue */ - CURL_TRC_M(admin, "Discarding connection #%" - FMT_OFF_T " from %zu to reach total " - "limit of %zu", - oldest_idle->connection_id, cpool->num_conn, total_limit); + CURL_TRC_M(admin, "Shutting down idle connection #%" + FMT_OFF_T " to '%s' due to total limit of %u", + oldest_idle->connection_id, oldest_idle->destination, + max_total); cpool_evict_conn(cpool, admin, oldest_idle); } - shutdowns = multi ? Curl_cshutdn_count(&multi->cshutdn) : 0; + /* bundle might be gone due to evictions */ + if(*pbundle) + *pbundle = cpool_find_bundle(cpool, to_add->destination); } - if((cpool->num_conn + shutdowns) >= total_limit) { - res = CPOOL_LIMIT_TOTAL; - goto out; + if(cshutdn) { + shutdowns = Curl_cshutdn_count(cshutdn); + if(shutdowns && (cpool->num_conn + shutdowns) >= max_total) { + size_t over = cpool->num_conn + shutdowns - max_total + 1; + shutdowns -= Curl_cshutdn_close_oldest(cshutdn, admin, NULL, over); + } } + if((cpool->num_conn + shutdowns) >= max_total) + return CPOOL_LIMIT_TOTAL; } -out: - CPOOL_UNLOCK(cpool, admin); - return res; + return CPOOL_LIMIT_OK; } CURLcode Curl_cpool_add(struct Curl_easy *data, struct connectdata *conn, + uint32_t max_total, + uint32_t max_host, const struct curltime *pnow) { CURLcode result = CURLE_OK; @@ -570,7 +568,24 @@ CURLcode Curl_cpool_add(struct Curl_easy *data, conn->shutdown.start_ms[SECONDARYSOCKET] = -1; CPOOL_LOCK(cpool, data); + + /* Find the bundle, should it exist, and check the limits */ bundle = cpool_find_bundle(cpool, conn->destination); + + switch(cpool_check_limits(data, max_total, conn, max_host, &bundle, pnow)) { + case CPOOL_LIMIT_DEST: + infof(data, "No more connections allowed to host"); + result = CURLE_NO_CONNECTION_AVAILABLE; + goto out; + case CPOOL_LIMIT_TOTAL: + infof(data, "No connections available, total of %u reached.", + data->multi->max_total_connections); + result = CURLE_NO_CONNECTION_AVAILABLE; + goto out; + default: + break; + } + if(!bundle) { bundle = cpool_add_bundle(cpool, conn->destination); if(!bundle) { @@ -645,11 +660,11 @@ static bool cpool_foreach(struct Curl_easy *data, * * Return TRUE if idle connection kept in pool, FALSE if closed. */ -bool Curl_cpool_conn_now_idle(struct Curl_easy *data, - struct connectdata *conn, - const struct curltime *pnow) +static bool cpool_conn_now_idle(struct cpool *cpool, + struct Curl_easy *data, + struct connectdata *conn, + const struct curltime *pnow) { - struct cpool *cpool = cpool_get_instance(data); struct connectdata *oldest_idle = NULL; struct Curl_easy *admin; unsigned int maxconnects; @@ -671,16 +686,8 @@ bool Curl_cpool_conn_now_idle(struct Curl_easy *data, maxconnects = data->multi->maxconnects; } - /* remember times, connection had been used just before */ - conn->lastchecked_ms = conn->lastupkeep_ms = conn->lastused_ms = - curlx_ptimediff_ms(pnow, &conn->created); if(cpool && maxconnects) { - /* may be called form a callback already under lock */ - bool do_lock = !CPOOL_IS_LOCKED(cpool); - admin = Curl_get_admin(data); - if(do_lock) - CPOOL_LOCK(cpool, admin); if(cpool->num_conn > maxconnects) { infof(data, "Connection pool is full, closing the oldest of %zu/%u", cpool->num_conn, maxconnects); @@ -691,15 +698,47 @@ bool Curl_cpool_conn_now_idle(struct Curl_easy *data, cpool_evict_conn(cpool, admin, oldest_idle); } } - if(do_lock) - CPOOL_UNLOCK(cpool, admin); } return kept; } +static void cpool_prune_dead(struct cpool *cpool, + struct Curl_easy *data, + const struct curltime *pnow); + +static bool cpool_find_act(struct cpool *cpool, + struct Curl_easy *data, + struct cpool_bundle *bundle, + struct connectdata *conn, + cpool_match_result match) +{ + switch(match) { + case CPOOL_MATCH_FOUND: + return TRUE; + case CPOOL_MATCH_TOO_OLD: + if(CONN_INUSE(conn)) + return FALSE; + FALLTHROUGH(); + case CPOOL_MATCH_CLOSE: + DEBUGASSERT(conn->bits.in_cpool); + if(conn->bits.in_cpool) { + cpool_bundle_remove(bundle, conn); + conn->bits.in_cpool = FALSE; + cpool->num_conn--; + } + cpool_conn_close(cpool, data, conn, FALSE); + break; + default: + break; + } + return FALSE; +} + bool Curl_cpool_find(struct Curl_easy *data, const char *destination, + bool prune_dead, + const struct curltime *pnow, Curl_cpool_conn_match_cb *conn_cb, Curl_cpool_done_match_cb *done_cb, void *userdata) @@ -716,6 +755,10 @@ bool Curl_cpool_find(struct Curl_easy *data, return FALSE; CPOOL_LOCK(cpool, data); + + if(prune_dead) + cpool_prune_dead(cpool, data, pnow); + bundle = Curl_hash_pick(&cpool->dest2bundle, destination, strlen(destination) + 1); if(bundle) { @@ -725,31 +768,32 @@ bool Curl_cpool_find(struct Curl_easy *data, * an authentication involving several requests is using * the same connection again. */ curr = Curl_llist_head(&bundle->conns); - while(curr) { + while(!found && curr) { conn = Curl_node_elem(curr); curr = Curl_node_next(curr); - if(data->state.lastconnect_id == conn->connection_id) { - if(conn_cb(conn, userdata)) - found = TRUE; - break; - } + if(data->state.lastconnect_id != conn->connection_id) + continue; + found = cpool_find_act(cpool, data, bundle, conn, + conn_cb(conn, userdata)); + break; } } if(!found) { curr = Curl_llist_head(&bundle->conns); - while(curr) { + while(!found && curr) { conn = Curl_node_elem(curr); curr = Curl_node_next(curr); - /* Already tried a matching connection above. No need to - * invoke callback again for this one. */ - if((data->state.lastconnect_id != conn->connection_id) && - conn_cb(conn, userdata)) { - found = TRUE; - break; - } + /* Already tried a matching connection above. */ + if(data->state.lastconnect_id == conn->connection_id) + continue; + found = cpool_find_act(cpool, data, bundle, conn, + conn_cb(conn, userdata)); } } + + if(!Curl_llist_count(&bundle->conns)) + cpool_remove_bundle(cpool, destination); } if(done_cb) { @@ -761,7 +805,7 @@ bool Curl_cpool_find(struct Curl_easy *data, struct cpool_reaper_ctx { size_t reaped; - struct curltime now; + const struct curltime *pnow; }; static int cpool_reap_dead_cb(struct cpool *cpool, @@ -772,7 +816,7 @@ static int cpool_reap_dead_cb(struct cpool *cpool, if(!CONN_INUSE(conn)) { if(conn->bits.no_reuse || conn->bits.close || - !Curl_cpool_conn_seems_healthy(conn, admin, &reaper->now)) { + !Curl_cpool_conn_seems_healthy(conn, admin, reaper->pnow)) { /* terminate conn and stop the iteration */ reaper->reaped++; cpool_conn_close(cpool, admin, conn, FALSE); @@ -786,32 +830,23 @@ static int cpool_reap_dead_cb(struct cpool *cpool, * This function scans the data's connection pool for half-open/dead * connections, closes and removes them. * The cleanup is done at most once per second. - * - * When called, this transfer has no connection attached. */ -void Curl_cpool_prune_dead(struct cpool *cpool, - struct Curl_easy *data) +static void cpool_prune_dead(struct cpool *cpool, + struct Curl_easy *data, + const struct curltime *pnow) { - struct Curl_easy *admin; - timediff_t elapsed; + timediff_t elapsed_ms = curlx_ptimediff_ms(pnow, &cpool->last_cleanup); - if(!cpool) - return; - - admin = Curl_get_admin(data); - CPOOL_LOCK(cpool, admin); - elapsed = curlx_ptimediff_ms(Curl_pgrs_now(admin), &cpool->last_cleanup); - - if(elapsed >= 1000L) { + if(elapsed_ms >= 1000L) { + struct Curl_easy *admin = Curl_get_admin(data); struct cpool_reaper_ctx reaper; memset(&reaper, 0, sizeof(reaper)); - reaper.now = *Curl_pgrs_now(admin); + reaper.pnow = pnow; while(cpool_foreach(admin, cpool, &reaper, cpool_reap_dead_cb)) ; - cpool->last_cleanup = *Curl_pgrs_now(admin); + cpool->last_cleanup = *pnow; } - CPOOL_UNLOCK(cpool, admin); } static int conn_upkeep(struct cpool *cpool, @@ -890,18 +925,42 @@ struct connectdata *Curl_cpool_get_conn(struct Curl_easy *data, return fctx.conn; } -void Curl_cpool_do_locked(struct Curl_easy *data, - struct connectdata *conn, - Curl_cpool_conn_do_cb *cb, void *cbdata) +void Curl_cpool_return(struct Curl_easy *data, + struct connectdata *conn, + Curl_cpool_return_cb *cb, void *cbdata, + const struct curltime *pnow) { struct cpool *cpool = cpool_get_instance(data); - if(cpool) { - CPOOL_LOCK(cpool, data); - cb(conn, data, cbdata); - CPOOL_UNLOCK(cpool, data); + + CPOOL_LOCK(cpool, data); + + /* remember times, connection had been used just before */ + conn->lastchecked_ms = conn->lastupkeep_ms = conn->lastused_ms = + curlx_ptimediff_ms(pnow, &conn->created); + + switch(cb(data, conn, cbdata, pnow)) { + case CPOOL_DO_KEEP: + break; + case CPOOL_DO_IDLE: + /* the connection is no longer in use by any transfer */ + if(cpool_conn_now_idle(cpool, data, conn, pnow)) { + /* connection kept in the cpool */ + infof(data, "Connection #%" FMT_OFF_T " to host %s:%u left intact", + conn->connection_id, conn->origin->user_hostname, + conn->origin->port); + } + else /* connection was removed from the cpool and destroyed. */ + data->state.lastconnect_id = -1; + break; + case CPOOL_DO_CLOSE: + Curl_conn_close(data, conn, FALSE); + break; + case CPOOL_DO_TERMINATE: + Curl_conn_close(data, conn, TRUE); + break; } - else - cb(conn, data, cbdata); + + CPOOL_UNLOCK(cpool, data); } static int cpool_mark_stale(struct cpool *cpool, @@ -941,9 +1000,9 @@ void Curl_cpool_nw_changed(struct cpool *cpool, struct Curl_easy *admin) /* A connection has to have been idle for less than 'conn_max_idle_ms' (the success rate is too low after this), or created less than 'conn_max_age_ms' ago, to be subject for reuse. */ -static bool cpool_conn_maxage(struct Curl_easy *data, - struct connectdata *conn, - const struct curltime *pnow) +static bool cpool_conn_too_old(struct Curl_easy *data, + struct connectdata *conn, + const struct curltime *pnow) { timediff_t age_ms; @@ -979,7 +1038,7 @@ bool Curl_cpool_conn_seems_healthy(struct connectdata *conn, bool healthy = TRUE; DEBUGASSERT(!data->conn); - if(!CONN_INUSE(conn) && cpool_conn_maxage(data, conn, pnow)) /* too old? */ + if(!CONN_INUSE(conn) && cpool_conn_too_old(data, conn, pnow)) return FALSE; if((curlx_ptimediff_ms(pnow, &conn->created) - conn->lastchecked_ms) < 1000) return TRUE; diff --git a/lib/conncache.h b/lib/conncache.h index 975d940b2fa9..cbe39438b723 100644 --- a/lib/conncache.h +++ b/lib/conncache.h @@ -83,6 +83,8 @@ struct connectdata *Curl_cpool_get_conn(struct Curl_easy *data, /* Add the connection to the pool. */ CURLcode Curl_cpool_add(struct Curl_easy *data, struct connectdata *conn, + uint32_t max_total, + uint32_t max_host, const struct curltime *pnow) WARN_UNUSED_RESULT; /* Connection was used at the given time. */ @@ -95,21 +97,16 @@ timediff_t Curl_cpool_conn_age_ms(struct Curl_easy *data, struct connectdata *conn, const struct curltime *pnow); -/** - * Return if the pool has reached its configured limits for adding - * the given connection. Try to discard the oldest, idle connections - * to make space. - */ -#define CPOOL_LIMIT_OK 0 -#define CPOOL_LIMIT_DEST 1 -#define CPOOL_LIMIT_TOTAL 2 -int Curl_cpool_check_limits(struct Curl_easy *data, - struct connectdata *conn, - const struct curltime *pnow); +typedef enum { + CPOOL_MATCH_FOUND, /* The passed `conn` matches, stop looking further */ + CPOOL_MATCH_CONT, /* No match, continue looking */ + CPOOL_MATCH_CLOSE, /* No match, close `conn`, continue looking */ + CPOOL_MATCH_TOO_OLD /* No match, `conn` is too old, close when idle */ +} cpool_match_result; /* Return of conn is suitable. If so, stops iteration. */ -typedef bool Curl_cpool_conn_match_cb(struct connectdata *conn, - void *userdata); +typedef cpool_match_result + Curl_cpool_conn_match_cb(struct connectdata *conn, void *userdata); /* Act on the result of the find, may override it. */ typedef bool Curl_cpool_done_match_cb(void *userdata); @@ -119,6 +116,8 @@ typedef bool Curl_cpool_done_match_cb(void *userdata); * All callbacks are invoked while the pool's lock is held. * @param data current transfer * @param destination match against `conn->destination` in pool + * @param prune_dead perform reaping of dead connections at start + * @param pnow timestamp of operation * @param conn_cb must be present, called for each connection in the * bundle until it returns TRUE * @return combined result of last conn_db and result_cb or FALSE if no @@ -126,36 +125,28 @@ typedef bool Curl_cpool_done_match_cb(void *userdata); */ bool Curl_cpool_find(struct Curl_easy *data, const char *destination, + bool prune_dead, + const struct curltime *pnow, Curl_cpool_conn_match_cb *conn_cb, Curl_cpool_done_match_cb *done_cb, void *userdata); -/* - * A connection (already in the pool) is now idle. Do any - * cleanups in regard to the pool's limits. - * - * Return TRUE if idle connection kept in pool, FALSE if closed. - */ -bool Curl_cpool_conn_now_idle(struct Curl_easy *data, - struct connectdata *conn, - const struct curltime *pnow); - -/** - * Scans the connection pool for half-open/dead - * connections, closes and removes them. - * The cleanup is done at most once per second. - */ -void Curl_cpool_prune_dead(struct cpool *cpool, - struct Curl_easy *data); - /** * Perform upkeep actions on connections in the transfer's pool. */ CURLcode Curl_cpool_upkeep(struct Curl_easy *data); -typedef void Curl_cpool_conn_do_cb(struct connectdata *conn, - struct Curl_easy *data, - void *cbdata); +typedef enum { + CPOOL_DO_KEEP, /* Keep the connection, still in use */ + CPOOL_DO_IDLE, /* Connection is idle, may get closed now */ + CPOOL_DO_CLOSE, /* Close the connection (clean) */ + CPOOL_DO_TERMINATE /* Terminate the connection (unclean) */ +} cpool_do_result; + +typedef cpool_do_result Curl_cpool_return_cb(struct Curl_easy *data, + struct connectdata *conn, + void *cbdata, + const struct curltime *pnow); /** * Invoked the callback for the given data + connection under the @@ -163,9 +154,10 @@ typedef void Curl_cpool_conn_do_cb(struct connectdata *conn, * The callback is always invoked, even if the transfer has no connection * pool associated. */ -void Curl_cpool_do_locked(struct Curl_easy *data, - struct connectdata *conn, - Curl_cpool_conn_do_cb *cb, void *cbdata); +void Curl_cpool_return(struct Curl_easy *data, + struct connectdata *conn, + Curl_cpool_return_cb *cb, void *cbdata, + const struct curltime *pnow); /* Close all unused connections, prevent reuse of existing ones. */ void Curl_cpool_nw_changed(struct cpool *cpool, struct Curl_easy *admin); diff --git a/lib/cshutdn.c b/lib/cshutdn.c index cca2db874efd..c49d52c9c8e9 100644 --- a/lib/cshutdn.c +++ b/lib/cshutdn.c @@ -277,42 +277,40 @@ void Curl_cshutdn_terminate(struct Curl_easy *admin, } } -static bool cshutdn_destroy_oldest(struct cshutdn *cshutdn, - struct Curl_easy *admin, - const char *destination) +static size_t cshutdn_destroy_oldest(struct cshutdn *cshutdn, + struct Curl_easy *admin, + const char *destination, + size_t count) { - struct Curl_llist_node *e; - struct connectdata *conn; - - e = Curl_llist_head(&cshutdn->list); - while(e) { - conn = Curl_node_elem(e); - if(!destination || !strcmp(destination, conn->destination)) - break; - e = Curl_node_next(e); - } + struct Curl_llist_node *e = Curl_llist_head(&cshutdn->list), *next; + struct Curl_sigpipe_ctx sigpipe_ctx; + size_t n = 0; - if(e) { - struct Curl_sigpipe_ctx sigpipe_ctx; - conn = Curl_node_elem(e); - Curl_node_remove(e); - sigpipe_init(&sigpipe_ctx); - sigpipe_apply(admin, &sigpipe_ctx); - Curl_cshutdn_terminate(admin, conn, FALSE); - sigpipe_restore(&sigpipe_ctx); - return TRUE; + sigpipe_init(&sigpipe_ctx); + while(e && (n < count)) { + struct connectdata *conn = Curl_node_elem(e); + next = Curl_node_next(e); + if(!destination || !strcmp(destination, conn->destination)) { + Curl_node_remove(e); + sigpipe_apply(admin, &sigpipe_ctx); + Curl_cshutdn_terminate(admin, conn, FALSE); + ++n; + } + e = next; } - return FALSE; + sigpipe_restore(&sigpipe_ctx); + return n; } -bool Curl_cshutdn_close_oldest(struct cshutdn *cshutdn, +size_t Curl_cshutdn_close_oldest(struct cshutdn *cshutdn, struct Curl_easy *admin, - const char *destination) + const char *destination, + size_t count) { if(cshutdn) { - return cshutdn_destroy_oldest(cshutdn, admin, destination); + return cshutdn_destroy_oldest(cshutdn, admin, destination, count); } - return FALSE; + return 0; } #define NUM_POLLS_ON_STACK 10 @@ -502,7 +500,7 @@ void Curl_cshutdn_add(struct cshutdn *cshutdn, if(max_shutdowns <= Curl_llist_count(&cshutdn->list)) { CURL_TRC_M(admin, "[SHUTDOWN] discarding oldest shutdown connection " "due to shutdown limit of %zu", max_shutdowns); - cshutdn_destroy_oldest(cshutdn, admin, NULL); + cshutdn_destroy_oldest(cshutdn, admin, NULL, 1); } if(multi->socket_cb && cshutdn_update_ev(multi, conn)) { diff --git a/lib/cshutdn.h b/lib/cshutdn.h index d6f7fb4215e0..efdcf1f55f63 100644 --- a/lib/cshutdn.h +++ b/lib/cshutdn.h @@ -92,12 +92,13 @@ size_t Curl_cshutdn_count(struct cshutdn *cshutdn); size_t Curl_cshutdn_dest_count(struct cshutdn *cshutdn, const char *destination); -/* Close the oldest connection in shutdown to destination or, +/* Close the oldest `count` connections in shutdown to destination or, * when destination is NULL for any destination. - * Return TRUE if a connection has been closed. */ -bool Curl_cshutdn_close_oldest(struct cshutdn *cshutdn, - struct Curl_easy *admin, - const char *destination); + * Return the number of connections closed. */ +size_t Curl_cshutdn_close_oldest(struct cshutdn *cshutdn, + struct Curl_easy *admin, + const char *destination, + size_t count); /* Add a connection to have it shut down. Terminate the oldest * connection when shutdowns exceed max_shutdowns. */ diff --git a/lib/http.c b/lib/http.c index 0aba9f2002d6..3ed023753d21 100644 --- a/lib/http.c +++ b/lib/http.c @@ -130,7 +130,7 @@ CURLcode Curl_http_setup_conn(struct Curl_easy *data, during this request */ if(data->state.http_neg.wanted == CURL_HTTP_V3x) { /* only HTTP/3, needs to work */ - CURLcode result = Curl_conn_may_http3(data, conn, conn->transport_wanted); + CURLcode result = Curl_conn_may_http3(data, conn); if(result) return result; } diff --git a/lib/multi.c b/lib/multi.c index b8520a0ed58e..17c0114d26f6 100644 --- a/lib/multi.c +++ b/lib/multi.c @@ -635,27 +635,22 @@ static bool multi_conn_should_close(struct connectdata *conn, return FALSE; } -static void multi_done_locked(struct connectdata *conn, - struct Curl_easy *data, - void *userdata) +static cpool_do_result multi_done_locked(struct Curl_easy *data, + struct connectdata *conn, + void *userdata, + const struct curltime *pnow) { struct multi_done_ctx *mdctx = userdata; - const struct curltime *pnow = Curl_pgrs_now(data); - - Curl_detach_connection(data); + (void)pnow; CURL_TRC_M(data, "multi_done_locked, in use=%u", conn->attached_xfers); if(CONN_INUSE(conn)) { /* Stop if still used. */ CURL_TRC_M(data, "Connection still in use %u, no more multi_done now!", conn->attached_xfers); - return; + return CPOOL_DO_KEEP; } - data->state.done = TRUE; /* called now! */ - - Curl_dnscache_prune(data, pnow); - if(multi_conn_should_close(conn, data, (bool)mdctx->premature)) { CURL_TRC_M(data, "multi_done, terminating conn #%" FMT_OFF_T " to %s:%u, " "forbid=%d, close=%d, premature=%d, conn_multiplex=%d", @@ -664,27 +659,17 @@ static void multi_done_locked(struct connectdata *conn, data->set.reuse_forbid, conn->bits.close, mdctx->premature, Curl_conn_is_multiplex(conn, FIRSTSOCKET)); connclose(conn); - Curl_conn_close(data, conn, (bool)mdctx->premature); + return mdctx->premature ? CPOOL_DO_TERMINATE : CPOOL_DO_CLOSE; } else if(!Curl_conn_get_max_concurrent(data, conn, FIRSTSOCKET)) { CURL_TRC_M(data, "multi_done, conn #%" FMT_OFF_T " to %s:%u was shutdown" " by server, not reusing", conn->connection_id, conn->origin->user_hostname, conn->origin->port); connclose(conn); - Curl_conn_close(data, conn, (bool)mdctx->premature); + return mdctx->premature ? CPOOL_DO_TERMINATE : CPOOL_DO_CLOSE; } else { - /* the connection is no longer in use by any transfer */ - if(Curl_cpool_conn_now_idle(data, conn, pnow)) { - /* connection kept in the cpool */ - infof(data, "Connection #%" FMT_OFF_T " to host %s:%u left intact", - conn->connection_id, conn->origin->user_hostname, - conn->origin->port); - } - else { - /* connection was removed from the cpool and destroyed. */ - data->state.lastconnect_id = -1; - } + return CPOOL_DO_IDLE; } } @@ -695,13 +680,16 @@ static CURLcode multi_done(struct Curl_easy *data, { CURLcode result; struct connectdata *conn = data->conn; + const struct curltime *pnow = Curl_pgrs_now(data); CURL_TRC_M(data, "multi_done: status: %d prem: %d done: %d", (int)status, (int)premature, data->state.done); - if(data->state.done) + if(data->state.done) { /* Stop if multi_done() has already been called */ return CURLE_OK; + } + data->state.done = TRUE; /* called now! */ /* Shut down any ongoing async resolver operation. */ Curl_resolv_shutdown_all(data); @@ -736,7 +724,7 @@ static CURLcode multi_done(struct Curl_easy *data, * - the transfer has not connected * - we already aborted by callback to avoid this calling another callback */ - int rc = Curl_pgrsDone(data); + int rc = Curl_pgrsDone(data, pnow); if(!result && rc) result = CURLE_ABORTED_BY_CALLBACK; } @@ -754,13 +742,15 @@ static CURLcode multi_done(struct Curl_easy *data, result = Curl_req_done(&data->req, data, premature); if(conn) { - /* Under the potential connection pool's share lock, decide what to - * do with the transfer's connection. */ + /* Detach connection and decide what to do with it. */ struct multi_done_ctx mdctx; + Curl_detach_connection(data); + Curl_dnscache_prune(data, pnow); + memset(&mdctx, 0, sizeof(mdctx)); mdctx.premature = premature; - Curl_cpool_do_locked(data, data->conn, multi_done_locked, &mdctx); + Curl_cpool_return(data, conn, multi_done_locked, &mdctx, pnow); } /* flush the netrc cache */ diff --git a/lib/progress.c b/lib/progress.c index fcb8bf24229b..c6432d68f733 100644 --- a/lib/progress.c +++ b/lib/progress.c @@ -185,11 +185,11 @@ const struct curltime *Curl_pgrs_now(struct Curl_easy *data) pgrsDone() - transfer complete */ -int Curl_pgrsDone(struct Curl_easy *data) +int Curl_pgrsDone(struct Curl_easy *data, const struct curltime *pnow) { int rc; data->progress.delta.lastshow_us = -1; - rc = Curl_pgrsUpdate(data); /* the final (forced) update */ + rc = Curl_pgrsUpdateX(data, pnow); /* the final (forced) update */ if(rc) return rc; diff --git a/lib/progress.h b/lib/progress.h index 19bb09517cbd..4554b34ca3f6 100644 --- a/lib/progress.h +++ b/lib/progress.h @@ -45,7 +45,7 @@ typedef enum { /* Get the current timestamp of the transfer */ const struct curltime *Curl_pgrs_now(struct Curl_easy *data); -int Curl_pgrsDone(struct Curl_easy *data); +int Curl_pgrsDone(struct Curl_easy *data, const struct curltime *pnow); void Curl_pgrsStart(struct Curl_easy *data, const struct curltime *pnow); void Curl_pgrsSetDownloadSize(struct Curl_easy *data, curl_off_t size); void Curl_pgrsSetUploadSize(struct Curl_easy *data, curl_off_t size); diff --git a/lib/tftp.c b/lib/tftp.c index a1413b3788e4..edc26739b3ef 100644 --- a/lib/tftp.c +++ b/lib/tftp.c @@ -1017,7 +1017,7 @@ static CURLcode tftp_done(struct Curl_easy *data, CURLcode status, (void)status; (void)premature; - if(Curl_pgrsDone(data)) + if(Curl_pgrsDone(data, Curl_pgrs_now(data))) return CURLE_ABORTED_BY_CALLBACK; /* If we have encountered an error */ diff --git a/lib/url.c b/lib/url.c index 99657a657ee4..60304a5f538e 100644 --- a/lib/url.c +++ b/lib/url.c @@ -983,50 +983,51 @@ static bool url_match_auth_nego(struct connectdata *conn, #define url_match_auth_nego(c, m) ((void)(c), (void)(m), TRUE) #endif -static bool url_match_conn(struct connectdata *conn, void *userdata) +static cpool_match_result url_match_conn(struct connectdata *conn, + void *userdata) { struct url_conn_match *m = userdata; bool wait_pipe = FALSE; /* general connect config setting match? */ if(!url_match_connect_config(conn, m)) - return FALSE; + return CPOOL_MATCH_CONT; /* match for destination and protocol? */ if(!url_match_destination(conn, m)) - return FALSE; + return CPOOL_MATCH_CONT; if(!url_match_fully_connected(conn, m)) - return FALSE; + return CPOOL_MATCH_CONT; if(!url_match_multiplex_needs(conn, m)) - return FALSE; + return CPOOL_MATCH_CONT; if(!url_match_ssl_use(conn, m)) - return FALSE; + return CPOOL_MATCH_CONT; if(!url_match_proxy_use(conn, m)) - return FALSE; + return CPOOL_MATCH_CONT; if(!url_match_ssl_config(conn, m)) - return FALSE; + return CPOOL_MATCH_CONT; if(!url_match_http_multiplex(conn, m, &wait_pipe)) - return FALSE; + return CPOOL_MATCH_CONT; if(!url_match_auth(conn, m)) - return FALSE; + return CPOOL_MATCH_CONT; if(!url_match_proto_config(conn, m)) - return FALSE; + return CPOOL_MATCH_CONT; if(!url_match_auth_ntlm(conn, m)) - return FALSE; + return CPOOL_MATCH_CONT; if(!url_match_auth_nego(conn, m)) - return FALSE; + return CPOOL_MATCH_CONT; if(!url_match_multiplex_limits(conn, m)) - return FALSE; + return CPOOL_MATCH_CONT; /* The connection matches all conditions, but do we want to use it? */ if(wait_pipe) { @@ -1034,16 +1035,14 @@ static bool url_match_conn(struct connectdata *conn, void *userdata) * yet. Put the transfer into PENDING and wait for conn state change. */ DEBUGASSERT(!m->found); m->wait_pipe = TRUE; - return TRUE; + return CPOOL_MATCH_FOUND; } if(m->data->set.conn_max_age_ms > 0) { timediff_t age_ms = Curl_cpool_conn_age_ms(m->data, conn, &m->now); if(age_ms > m->data->set.conn_max_age_ms) { /* Transfer is looking for a younger connection. */ - if(!CONN_INUSE(conn)) - Curl_conn_close(m->data, conn, FALSE); - return FALSE; + return CPOOL_MATCH_TOO_OLD; } } @@ -1053,13 +1052,12 @@ static bool url_match_conn(struct connectdata *conn, void *userdata) !Curl_cpool_conn_seems_healthy(conn, m->data, &m->now)) { infof(m->data, "Connection %" FMT_OFF_T " seems to be dead, terminating", conn->connection_id); - Curl_conn_close(m->data, conn, FALSE); - return FALSE; + return CPOOL_MATCH_CLOSE; } /* conn matches our needs. */ m->found = conn; - return TRUE; + return CPOOL_MATCH_FOUND; } static bool url_match_result(void *userdata) @@ -1096,21 +1094,13 @@ static bool url_match_result(void *userdata) */ static bool url_attach_existing(struct Curl_easy *data, struct connectdata *needle, - struct url_conn_match *m) + struct url_conn_match *m, + const struct curltime *pnow) { - struct cpool *cpool = Curl_cpool_get_instance(data); - bool success; - - DEBUGASSERT(!data->conn); - - Curl_cpool_prune_dead(cpool, data); - /* Find a connection in the pool that matches what "data + needle" * requires. If a suitable candidate is found, it is attached to "data". */ - success = Curl_cpool_find(data, needle->destination, - url_match_conn, url_match_result, m); - - return success; + return Curl_cpool_find(data, needle->destination, TRUE, pnow, + url_match_conn, url_match_result, m); } /* @@ -1522,10 +1512,9 @@ static CURLcode setup_connection_internals(struct Curl_easy *data, struct connectdata *conn) { struct Curl_peer *peer = NULL; - CURLcode result; if(conn->scheme->run->setup_connection) { - result = conn->scheme->run->setup_connection(data, conn); + CURLcode result = conn->scheme->run->setup_connection(data, conn); if(result) return result; } @@ -1556,10 +1545,10 @@ static CURLcode setup_connection_internals(struct Curl_easy *data, if(data->set.scope_id) conn->scope_id = data->set.scope_id; else { - struct Curl_peer *first = Curl_conn_get_first_peer(conn, FIRSTSOCKET); - if(!first) + peer = Curl_conn_get_first_peer(conn, FIRSTSOCKET); + if(!peer) return CURLE_FAILED_INIT; - conn->scope_id = first->scopeid; + conn->scope_id = peer->scopeid; } #endif @@ -2269,7 +2258,7 @@ static CURLcode url_find_or_create_conn(struct Curl_easy *data, goto out; /* Setup a "faked" transfer that will do nothing */ - result = Curl_cpool_add(data, needle, pnow); + result = Curl_cpool_add(data, needle, 0, 0, pnow); Curl_attach_connection(data, needle, TRUE); needle = NULL; if(!result) { @@ -2299,40 +2288,42 @@ static CURLcode url_find_or_create_conn(struct Curl_easy *data, if((!data->set.reuse_fresh || data->state.followlocation) && !data->set.connect_only) { /* Ok, try to find and attach an existing one */ - url_attach_existing(data, needle, &match); - } - - if(data->conn) { - /* We attached an existing connection for this transfer. Copy - * over transfer specific properties over from needle. */ - struct connectdata *conn = data->conn; - VERBOSE(bool tls_upgraded = - (!(needle->origin->scheme->flags & PROTOPT_SSL) && - Curl_conn_is_ssl(conn, FIRSTSOCKET))); - - conn->bits.reuse = TRUE; - url_conn_reuse_adjust(data, needle); + if(url_attach_existing(data, needle, &match, pnow)) { + /* We attached an existing connection for this transfer. Copy + * over transfer specific properties over from needle. */ + struct connectdata *conn = data->conn; + VERBOSE(bool tls_upgraded = + (!(needle->origin->scheme->flags & PROTOPT_SSL) && + Curl_conn_is_ssl(conn, FIRSTSOCKET))); + + DEBUGASSERT(conn); + conn->bits.reuse = TRUE; + url_conn_reuse_adjust(data, needle); #ifndef CURL_DISABLE_PROXY - infof(data, "Reusing existing %s: connection%s with %s %s", - conn->origin->scheme->name, - tls_upgraded ? " (upgraded to SSL)" : "", - (conn->socks_proxy.peer || conn->http_proxy.peer) ? "proxy" : "host", - conn->socks_proxy.peer ? conn->socks_proxy.peer->user_hostname : - conn->http_proxy.peer ? conn->http_proxy.peer->user_hostname : - conn->origin->hostname); + infof(data, "Reusing existing %s: connection%s with %s %s", + conn->origin->scheme->name, + tls_upgraded ? " (upgraded to SSL)" : "", + (conn->socks_proxy.peer || conn->http_proxy.peer) ? + "proxy" : "host", + conn->socks_proxy.peer ? conn->socks_proxy.peer->user_hostname : + conn->http_proxy.peer ? conn->http_proxy.peer->user_hostname : + conn->origin->hostname); #else - infof(data, "Reusing existing %s: connection%s with host %s", - conn->origin->scheme->name, - tls_upgraded ? " (upgraded to SSL)" : "", - conn->origin->hostname); + infof(data, "Reusing existing %s: connection%s with host %s", + conn->origin->scheme->name, + tls_upgraded ? " (upgraded to SSL)" : "", + conn->origin->hostname); #endif + } } - else { - /* We have decided that we want a new connection. We may not be able to do - that if we have reached the limit of how many connections we are - allowed to open. */ + if(!data->conn) { + /* We have not attached an existing connection. `needle` may become + * the connection to use, if + * - we are not waiting on an existing connection result + * - the total/host limits on active connections allow it + */ if(match.wait_pipe) { /* There is a connection that *might* become usable for multiplexing "soon", and we wait for that */ @@ -2340,50 +2331,34 @@ static CURLcode url_find_or_create_conn(struct Curl_easy *data, result = CURLE_NO_CONNECTION_AVAILABLE; goto out; } - else { - switch(Curl_cpool_check_limits(data, needle, pnow)) { - case CPOOL_LIMIT_DEST: - infof(data, "No more connections allowed to host"); - result = CURLE_NO_CONNECTION_AVAILABLE; - goto out; - case CPOOL_LIMIT_TOTAL: - if(data->master_mid != UINT32_MAX) - CURL_TRC_M(data, "Allowing sub-requests (like DoH) to override " - "max connection limit"); - else { - infof(data, "No connections available, total of %u reached.", - data->multi->max_total_connections); - result = CURLE_NO_CONNECTION_AVAILABLE; - goto out; - } - break; - default: - break; - } - } - /* Convert needle into a full connection by cloning the - * ssl filter config used in matching into the connection. */ + /* Add needle to conn pool, observing the limits. + * Initializes connection_id. + * May fail due to limits with CURLE_NO_CONNECTION_AVAILABLE. */ + result = Curl_cpool_add(data, needle, + data->multi->max_total_connections, + data->multi->max_host_connections, + pnow); + if(result) + goto out; + + /* All fine, attach it and forget needle */ + Curl_attach_connection(data, needle, TRUE); + needle = NULL; + + /* Clone the SSL filter configs into the new connection. */ result = Curl_ssl_conn_config_clone(&match.ssl_config, #ifndef CURL_DISABLE_PROXY &match.proxy_ssl_config, #else NULL, #endif - needle); + data->conn); if(result) { DEBUGF(infof(data, "Error: clone connection SSL config")); goto out; } - /* Add needle to conn pool, which assigns the connection id. - * Attach regardless of result, for correct handling. */ - result = Curl_cpool_add(data, needle, pnow); - Curl_attach_connection(data, needle, TRUE); - needle = NULL; - if(result) - goto out; - #ifdef USE_NTLM /* If NTLM is requested in a part of this connection, make sure we do not assume the state is fine as this is a fresh connection and NTLM is @@ -2404,6 +2379,8 @@ static CURLcode url_find_or_create_conn(struct Curl_easy *data, #endif } + /* COMMON setup code for reused and new connections from here on. */ + /* Setup and init stuff before DO starts, in preparing for the transfer. */ result = Curl_init_transfer(data, data->conn); if(result) diff --git a/lib/vquic/vquic.c b/lib/vquic/vquic.c index c6371368bdd4..4a8b42774ace 100644 --- a/lib/vquic/vquic.c +++ b/lib/vquic/vquic.c @@ -51,6 +51,7 @@ #include "bufq.h" #include "curlx/dynbuf.h" #include "curlx/win32-fopen.h" +#include "connect.h" #include "cfilters.h" #include "vdns/cf-dns.h" #include "vquic/cf-ngtcp2.h" @@ -1192,10 +1193,9 @@ CURLcode Curl_cf_h3_proxy_create(struct Curl_cfilter **pcf, #endif /* !CURL_DISABLE_PROXY && USE_PROXY_HTTP3 */ CURLcode Curl_conn_may_http3(struct Curl_easy *data, - const struct connectdata *conn, - unsigned char transport) + struct connectdata *conn) { - if(transport == TRNSPRT_UNIX) { + if(Curl_conn_get_first_peer(conn, FIRSTSOCKET)->unix_socket) { failf(data, "HTTP/3 cannot be used over UNIX domain sockets"); return CURLE_QUIC_CONNECT_ERROR; } @@ -1324,12 +1324,10 @@ struct nghttp3_mem *Curl_nghttp3_mem(void) #else /* CURL_DISABLE_HTTP || !USE_HTTP3 */ CURLcode Curl_conn_may_http3(struct Curl_easy *data, - const struct connectdata *conn, - unsigned char transport) + struct connectdata *conn) { (void)data; (void)conn; - (void)transport; DEBUGF(infof(data, "QUIC is not supported in this build")); return CURLE_NOT_BUILT_IN; } diff --git a/lib/vquic/vquic.h b/lib/vquic/vquic.h index f1363e2c0a76..eea440d67b9f 100644 --- a/lib/vquic/vquic.h +++ b/lib/vquic/vquic.h @@ -86,7 +86,6 @@ extern struct Curl_cftype Curl_cft_h3_proxy; #endif /* !CURL_DISABLE_HTTP && USE_HTTP3 */ CURLcode Curl_conn_may_http3(struct Curl_easy *data, - const struct connectdata *conn, - unsigned char transport); + struct connectdata *conn); #endif /* HEADER_CURL_VQUIC_QUIC_H */ diff --git a/lib/vssh/libssh.c b/lib/vssh/libssh.c index ca607be3aa21..6d2a1e2d24cc 100644 --- a/lib/vssh/libssh.c +++ b/lib/vssh/libssh.c @@ -2760,7 +2760,7 @@ static CURLcode myssh_done(struct Curl_easy *data, else result = status; - if(Curl_pgrsDone(data)) + if(Curl_pgrsDone(data, Curl_pgrs_now(data))) return CURLE_ABORTED_BY_CALLBACK; CURL_REQ_CLEAR_IO(data); diff --git a/lib/vssh/libssh2.c b/lib/vssh/libssh2.c index 2096fe34946f..b950b88652ec 100644 --- a/lib/vssh/libssh2.c +++ b/lib/vssh/libssh2.c @@ -3620,7 +3620,7 @@ static CURLcode ssh_done(struct Curl_easy *data, CURLcode status) else result = status; - if(Curl_pgrsDone(data)) + if(Curl_pgrsDone(data, Curl_pgrs_now(data))) return CURLE_ABORTED_BY_CALLBACK; CURL_REQ_CLEAR_IO(data); diff --git a/tests/data/test1554 b/tests/data/test1554 index 38ea1feaa387..bc8afb86a447 100644 --- a/tests/data/test1554 +++ b/tests/data/test1554 @@ -28,8 +28,6 @@ run 1: foobar and so on fun! [- Mutex unlock CONNECT -] Mutex lock CONNECT [- Mutex unlock CONNECT --] Mutex lock CONNECT -[- Mutex unlock CONNECT run 1: foobar and so on fun! -] Mutex lock CONNECT [- Mutex unlock CONNECT @@ -41,8 +39,6 @@ run 1: foobar and so on fun! [- Mutex unlock CONNECT -] Mutex lock CONNECT [- Mutex unlock CONNECT --] Mutex lock CONNECT -[- Mutex unlock CONNECT run 1: foobar and so on fun! -] Mutex lock CONNECT [- Mutex unlock CONNECT @@ -54,8 +50,6 @@ run 1: foobar and so on fun! [- Mutex unlock CONNECT -] Mutex lock CONNECT [- Mutex unlock CONNECT --] Mutex lock CONNECT -[- Mutex unlock CONNECT run 1: foobar and so on fun! -] Mutex lock CONNECT [- Mutex unlock CONNECT From 15bc69a944f29f69c39b08773847525f56310978 Mon Sep 17 00:00:00 2001 From: Daniel Stenberg Date: Fri, 18 Sep 2026 00:15:50 +0200 Subject: [PATCH 170/481] strparse: speed up number parsing By using a cutoff value for looping simpler while not close to the max. Runs the numparser test 15-17% faster for me. Closes #22993 --- lib/curlx/strparse.c | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/lib/curlx/strparse.c b/lib/curlx/strparse.c index 9e8fe4963d53..a17aa3c137e0 100644 --- a/lib/curlx/strparse.c +++ b/lib/curlx/strparse.c @@ -191,12 +191,17 @@ static int str_num_base(const char **linep, curl_off_t *nump, curl_off_t max, } while(valid_digit(*p, m)); } else { - do { - int n = curlx_hexval(*p++); - if(num > ((max - n) / base)) - return STRE_OVERFLOW; - num = (num * base) + n; - } while(valid_digit(*p, m)); + curl_off_t cutoff = (max - (base - 1)) / base; + while(valid_digit(*p, m)) { + if(num <= cutoff) + num = (num * base) + curlx_hexval(*p++); + else { + int n = curlx_hexval(*p++); + if(num > ((max - n) / base)) + return STRE_OVERFLOW; + num = (num * base) + n; + } + } } *nump = num; *linep = p; From 7f41af8d5df18d04e95e581d55d650aac8af0654 Mon Sep 17 00:00:00 2001 From: Joshua Rogers Date: Fri, 18 Sep 2026 19:56:57 +0200 Subject: [PATCH 171/481] formdata: reject a negative content length in curl_formadd CURLFORM_CONTENTSLENGTH and CURLFORM_CONTENTLEN accepted a negative length, which reached forminfo_copyfield() and the mime conversion as a near-SIZE_MAX size_t. Return CURL_FORMADD_INCOMPLETE instead. Closes #22996 --- lib/formdata.c | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/lib/formdata.c b/lib/formdata.c index 60b65a2c21ad..825333e60af3 100644 --- a/lib/formdata.c +++ b/lib/formdata.c @@ -400,14 +400,25 @@ static CURLFORMcode formadd(struct curl_httppost **httppost, retval = CURL_FORMADD_NULL; } break; - case CURLFORM_CONTENTSLENGTH: - curr->contentslength = (curl_off_t)(size_t)form_int_arg(long); + case CURLFORM_CONTENTSLENGTH: { + long clen = form_int_arg(long); + if(clen < 0) + retval = CURL_FORMADD_INCOMPLETE; + else + curr->contentslength = (curl_off_t)clen; break; + } - case CURLFORM_CONTENTLEN: - curr->flags |= CURL_HTTPPOST_LARGE; - curr->contentslength = form_int_arg(curl_off_t); + case CURLFORM_CONTENTLEN: { + curl_off_t clen = form_int_arg(curl_off_t); + if(clen < 0) + retval = CURL_FORMADD_INCOMPLETE; + else { + curr->flags |= CURL_HTTPPOST_LARGE; + curr->contentslength = clen; + } break; + } /* Get contents from a given filename */ case CURLFORM_FILECONTENT: From 67e631a278fd79045de29f9f0ff65f58cef3a8ca Mon Sep 17 00:00:00 2001 From: Joshua Rogers Date: Fri, 18 Sep 2026 20:23:25 +0200 Subject: [PATCH 172/481] tool_getparam: do not print the value of a redacted argument The Unicode warning printed the full argument, including for ARG_CLEAR options such as --user and --oauth2-bearer whose values cleanarg() wipes from argv to keep them out of the process list. Print the option name instead for those. Closes #22997 --- src/tool_getparam.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/tool_getparam.c b/src/tool_getparam.c index 1d2a70b97387..50330fc02c25 100644 --- a/src/tool_getparam.c +++ b/src/tool_getparam.c @@ -3063,8 +3063,12 @@ ParameterError getparameter(const char *flag, /* f or -long-flag */ } if(has_leading_unicode((const unsigned char *)nextarg)) { - warnf("The argument '%s' starts with a Unicode character. " - "Maybe ASCII was intended?", nextarg); + if(a->desc & ARG_CLEAR) + warnf("The argument to --%s starts with a Unicode character. " + "Maybe ASCII was intended?", a->lname); + else + warnf("The argument '%s' starts with a Unicode character. " + "Maybe ASCII was intended?", nextarg); } switch(ARGTYPE(a->desc)) { case ARG_FILE: From 7f364029b861d064caa128f4a8cb7b34696f7db3 Mon Sep 17 00:00:00 2001 From: Joshua Rogers Date: Fri, 18 Sep 2026 16:58:11 +0200 Subject: [PATCH 173/481] ftp: require TLS on the data connection for implicit FTPS A bare ftps:// URL leaves CURLOPT_USE_SSL at CURLUSESSL_NONE, so a server rejecting PROT P got a cleartext data connection and CURLE_OK. Treat an implicit TLS scheme as CURLUSESSL_ALL when deciding the data connection protection level, without changing ftp_conn.use_ssl, which takes part in connection reuse matching. Test 1790. Closes #22995 --- lib/ftp.c | 16 +++++++++++--- tests/data/Makefile.am | 1 + tests/data/test1790 | 48 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 62 insertions(+), 3 deletions(-) create mode 100644 tests/data/test1790 diff --git a/lib/ftp.c b/lib/ftp.c index ae79592b23e9..d4810eb15f40 100644 --- a/lib/ftp.c +++ b/lib/ftp.c @@ -3168,6 +3168,15 @@ static CURLcode ftp_wait_resp(struct Curl_easy *data, return result; } +/* an implicit TLS scheme implies TLS on both connections */ +static unsigned char ftp_use_ssl(struct connectdata *conn, + struct ftp_conn *ftpc) +{ + if(!ftpc->use_ssl && (conn->scheme->flags & PROTOPT_SSL)) + return CURLUSESSL_ALL; + return ftpc->use_ssl; +} + static CURLcode ftp_pp_statemachine(struct Curl_easy *data, struct connectdata *conn) { @@ -3254,7 +3263,7 @@ static CURLcode ftp_pp_statemachine(struct Curl_easy *data, case FTP_PBSZ: result = Curl_pp_sendf(data, &ftpc->pp, "PROT %c", - ftpc->use_ssl == CURLUSESSL_CONTROL ? 'C' : 'P'); + ftp_use_ssl(conn, ftpc) == CURLUSESSL_CONTROL ? 'C' : 'P'); if(!result) ftp_state(data, ftpc, FTP_PROT); break; @@ -3262,10 +3271,11 @@ static CURLcode ftp_pp_statemachine(struct Curl_easy *data, case FTP_PROT: if(ftpcode / 100 == 2) /* We have enabled SSL for the data connection! */ - conn->bits.ftp_use_data_ssl = (ftpc->use_ssl != CURLUSESSL_CONTROL); + conn->bits.ftp_use_data_ssl = + (ftp_use_ssl(conn, ftpc) != CURLUSESSL_CONTROL); /* FTP servers typically responds with 500 if they decide to reject our 'P' request */ - else if(ftpc->use_ssl > CURLUSESSL_CONTROL) + else if(ftp_use_ssl(conn, ftpc) > CURLUSESSL_CONTROL) /* we failed and bails out */ return CURLE_USE_SSL_FAILED; diff --git a/tests/data/Makefile.am b/tests/data/Makefile.am index 86cd82464e7c..8cc9cfc40170 100644 --- a/tests/data/Makefile.am +++ b/tests/data/Makefile.am @@ -1734,6 +1734,7 @@ TESTCASES = \ test1725 \ test1740 \ test1741 \ + test1790 \ test1791 \ test1800 \ test1801 \ diff --git a/tests/data/test1790 b/tests/data/test1790 new file mode 100644 index 000000000000..80bf7ca5f8c7 --- /dev/null +++ b/tests/data/test1790 @@ -0,0 +1,48 @@ + + + + +FTP +FTPS +PASV +LIST + + + +# Server-side + + +REPLY "PROT P" 534 Policy denies SSL + + + +# Client-side + + +SSL + + +ftps + + +FTPS with rejected PROT P + + +--insecure ftps://%HOSTIP:%FTPSPORT/ + + + +# Verify data after the test has been "shot" + +# 64 == CURLE_USE_SSL_FAILED + +64 + + +USER anonymous +PASS ftp@example.com +PBSZ 0 +PROT P + + + From f1956d23051657ebdc98e712f19e49eebdf506de Mon Sep 17 00:00:00 2001 From: Daniel Stenberg Date: Fri, 18 Sep 2026 23:11:53 +0200 Subject: [PATCH 174/481] parsedate: time zone strings The single and two letter names are not used for lookup anymore so drop them. The three and four letter versions are now stored as uint32 to use less memory and get found slightly faster. Closes #23000 --- lib/parsedate.c | 187 +++++++++++++++++++++++++----------------------- 1 file changed, 97 insertions(+), 90 deletions(-) diff --git a/lib/parsedate.c b/lib/parsedate.c index b4dd97bb7f70..9630a988b962 100644 --- a/lib/parsedate.c +++ b/lib/parsedate.c @@ -108,96 +108,99 @@ static const char * const weekday[] = { }; struct tzinfo { - char name[5]; + uint32_t tz; int16_t offset; /* +/- in minutes */ }; -#define tDAYZONE (-60) /* offset for daylight savings time */ - -/* alpha-sorted list of single-letter time zones */ -static const struct tzinfo tzone[] = { - { "A", -1 * 60 }, /* Alpha */ - { "B", -2 * 60 }, /* Bravo */ - { "C", -3 * 60 }, /* Charlie */ - { "D", -4 * 60 }, /* Delta */ - { "E", -5 * 60 }, /* Echo */ - { "F", -6 * 60 }, /* Foxtrot */ - { "G", -7 * 60 }, /* Golf */ - { "H", -8 * 60 }, /* Hotel */ - { "I", -9 * 60 }, /* India */ - { "", -1 }, /* not a timezone */ - { "K", -10 * 60 }, /* Kilo */ - { "L", -11 * 60 }, /* Lima */ - { "M", -12 * 60 }, /* Mike */ - { "N", 60 }, /* November */ - { "O", 2 * 60 }, /* Oscar */ - { "P", 3 * 60 }, /* Papa */ - { "Q", 4 * 60 }, /* Quebec */ - { "R", 5 * 60 }, /* Romeo */ - { "S", 6 * 60 }, /* Sierra */ - { "T", 7 * 60 }, /* Tango */ - { "U", 8 * 60 }, /* Uniform */ - { "V", 9 * 60 }, /* Victor */ - { "W", 10 * 60 }, /* Whiskey */ - { "X", 11 * 60 }, /* X-ray */ - { "Y", 12 * 60 }, /* Yankee */ - { "Z", 0 }, /* Zulu, zero meridian, a.k.a. UTC */ +#define DST (-60) /* offset for daylight savings time */ + +/* alpha-sorted list of single-letter time zones, A - Z */ +static const int16_t tzone[] = { + -1 * 60, /* Alpha */ + -2 * 60, /* Bravo */ + -3 * 60, /* Charlie */ + -4 * 60, /* Delta */ + -5 * 60, /* Echo */ + -6 * 60, /* Foxtrot */ + -7 * 60, /* Golf */ + -8 * 60, /* Hotel */ + -9 * 60, /* India */ + -1, /* J - not a timezone */ + -10 * 60, /* Kilo */ + -11 * 60, /* Lima */ + -12 * 60, /* Mike */ + 60, /* November */ + 2 * 60, /* Oscar */ + 3 * 60, /* Papa */ + 4 * 60, /* Quebec */ + 5 * 60, /* Romeo */ + 6 * 60, /* Sierra */ + 7 * 60, /* Tango */ + 8 * 60, /* Uniform */ + 9 * 60, /* Victor */ + 10 * 60, /* Whiskey */ + 11 * 60, /* X-ray */ + 12 * 60, /* Yankee */ + 0 /* Zulu, zero meridian, a.k.a. UTC */ }; -/* two-letter time zones */ -static const struct tzinfo tztwo[] = { - { "NT", 660 }, /* Nome */ /* spellchecker:disable-line */ - { "UT", 0 }, /* Universal Time */ -}; +/* two-letter time zones + + 11 * 60, NT - Nome spellchecker:disable-line + 0, UT - Universal Time + +*/ + +#define MKTZ(a,b,c,d) (((a)<<24) | ((b)<<16) | ((c)<<8) | (d)) /* three-letter time zones */ static const struct tzinfo tzthree[] = { - { "ADT", 240 + tDAYZONE }, /* Atlantic Daylight */ - { "AST", 240 }, /* Atlantic Standard */ - { "BST", 0 + tDAYZONE }, /* British Summer */ - { "CAT", 600 }, /* Central Alaska */ - { "CCT", -480 }, /* China Coast, USSR Zone 7 */ - { "CDT", 360 + tDAYZONE }, /* Central Daylight */ - { "CET", -60 }, /* Central European */ - { "CST", 360 }, /* Central Standard */ - { "EDT", 300 + tDAYZONE }, /* Eastern Daylight */ - { "EET", -120 }, /* Eastern Europe, USSR Zone 1 */ - { "EST", 300 }, /* Eastern Standard */ - { "FST", -60 + tDAYZONE }, /* French Summer */ - { "FWT", -60 }, /* French Winter */ - { "GMT", 0 }, /* Greenwich Mean */ - { "GST", -600 }, /* Guam Standard, USSR Zone 9 */ - { "HDT", 600 + tDAYZONE }, /* Hawaii Daylight */ - { "HST", 600 }, /* Hawaii Standard */ - { "JST", -540 }, /* Japan Standard, USSR Zone 8 */ - { "MDT", 420 + tDAYZONE }, /* Mountain Daylight */ - { "MET", -60 }, /* Middle European */ - { "MST", 420 }, /* Mountain Standard */ - { "NZT", -720 }, /* New Zealand */ - { "PDT", 480 + tDAYZONE }, /* Pacific Daylight */ - { "PST", 480 }, /* Pacific Standard */ - { "UTC", 0 }, /* Universal (Coordinated) */ - { "WAT", 60 }, /* West Africa */ - { "WET", 0 }, /* Western European */ - { "YDT", 540 + tDAYZONE }, /* Yukon Daylight */ - { "YST", 540 }, /* Yukon Standard */ + { MKTZ('A', 'D', 'T', '\0'), 240 + DST }, /* Atlantic Daylight */ + { MKTZ('A', 'S', 'T', '\0'), 240 }, /* Atlantic Standard */ + { MKTZ('B', 'S', 'T', '\0'), 0 + DST }, /* British Summer */ + { MKTZ('C', 'A', 'T', '\0'), 600 }, /* Central Alaska */ + { MKTZ('C', 'C', 'T', '\0'), -480 }, /* China Coast, USSR Zone 7 */ + { MKTZ('C', 'D', 'T', '\0'), 360 + DST }, /* Central Daylight */ + { MKTZ('C', 'E', 'T', '\0'), -60 }, /* Central European */ + { MKTZ('C', 'S', 'T', '\0'), 360 }, /* Central Standard */ + { MKTZ('E', 'D', 'T', '\0'), 300 + DST }, /* Eastern Daylight */ + { MKTZ('E', 'E', 'T', '\0'), -120 }, /* Eastern Europe, USSR Zone 1 */ + { MKTZ('E', 'S', 'T', '\0'), 300 }, /* Eastern Standard */ + { MKTZ('F', 'S', 'T', '\0'), -60 + DST }, /* French Summer */ + { MKTZ('F', 'W', 'T', '\0'), -60 }, /* French Winter */ + { MKTZ('G', 'M', 'T', '\0'), 0 }, /* Greenwich Mean */ + { MKTZ('G', 'S', 'T', '\0'), -600 }, /* Guam Standard, USSR Zone 9 */ + { MKTZ('H', 'D', 'T', '\0'), 600 + DST }, /* Hawaii Daylight */ + { MKTZ('H', 'S', 'T', '\0'), 600 }, /* Hawaii Standard */ + { MKTZ('J', 'S', 'T', '\0'), -540 }, /* Japan Standard, USSR Zone 8 */ + { MKTZ('M', 'D', 'T', '\0'), 420 + DST }, /* Mountain Daylight */ + { MKTZ('M', 'E', 'T', '\0'), -60 }, /* Middle European */ + { MKTZ('M', 'S', 'T', '\0'), 420 }, /* Mountain Standard */ + { MKTZ('N', 'Z', 'T', '\0'), -720 }, /* New Zealand */ + { MKTZ('P', 'D', 'T', '\0'), 480 + DST }, /* Pacific Daylight */ + { MKTZ('P', 'S', 'T', '\0'), 480 }, /* Pacific Standard */ + { MKTZ('U', 'T', 'C', '\0'), 0 }, /* Universal (Coordinated) */ + { MKTZ('W', 'A', 'T', '\0'), 60 }, /* West Africa */ + { MKTZ('W', 'E', 'T', '\0'), 0 }, /* Western European */ + { MKTZ('Y', 'D', 'T', '\0'), 540 + DST }, /* Yukon Daylight */ + { MKTZ('Y', 'S', 'T', '\0'), 540 }, /* Yukon Standard */ }; /* four-letter time zones */ static const struct tzinfo tzfoura[] = { - { "AHST", 600 }, /* Alaska-Hawaii Standard */ - { "CEST", -60 + tDAYZONE }, /* Central European Summer */ - { "EADT", -600 + tDAYZONE }, /* Eastern Australian Daylight */ - { "EAST", -600 }, /* Eastern Australian Standard */ - { "IDLE", -720 }, /* International Date Line East */ - { "IDLW", 720 }, /* International Date Line West */ - { "MEST", -60 + tDAYZONE }, /* Middle European Summer */ - { "MESZ", -60 + tDAYZONE }, /* Middle European Summer */ - { "MEWT", -60 }, /* Middle European Winter */ - { "NZDT", -720 + tDAYZONE }, /* New Zealand Daylight */ - { "NZST", -720 }, /* New Zealand Standard */ - { "WADT", -420 + tDAYZONE }, /* West Australian Daylight */ - { "WAST", -420 }, /* spellchecker:disable-line */ + { MKTZ('A', 'H', 'S', 'T'), 600 }, /* Alaska-Hawaii Standard */ + { MKTZ('C', 'E', 'S', 'T'), -60 + DST }, /* Central European Summer */ + { MKTZ('E', 'A', 'D', 'T'), -600 + DST }, /* Eastern Australian Daylight */ + { MKTZ('E', 'A', 'S', 'T'), -600 }, /* Eastern Australian Standard */ + { MKTZ('I', 'D', 'L', 'E'), -720 }, /* International Date Line East */ + { MKTZ('I', 'D', 'L', 'W'), 720 }, /* International Date Line West */ + { MKTZ('M', 'E', 'S', 'T'), -60 + DST }, /* Middle European Summer */ + { MKTZ('M', 'E', 'S', 'Z'), -60 + DST }, /* Middle European Summer */ + { MKTZ('M', 'E', 'W', 'T'), -60 }, /* Middle European Winter */ + { MKTZ('N', 'Z', 'D', 'T'), -720 + DST }, /* New Zealand Daylight */ + { MKTZ('N', 'Z', 'S', 'T'), -720 }, /* New Zealand Standard */ + { MKTZ('W', 'A', 'D', 'T'), -420 + DST }, /* West Australian Daylight */ + { MKTZ('W', 'A', 'S', 'T'), -420 }, /* spellchecker:disable-line */ /* West Australian Standard */ }; @@ -320,7 +323,7 @@ static int tzcompare(const void *m1, const void *m2) { const struct tzinfo *tz1 = m1; const struct tzinfo *tz2 = m2; - return strcmp(tz1->name, tz2->name); + return tz1->tz - tz2->tz; } /* return the time zone offset between GMT and the input one, in number of @@ -331,21 +334,25 @@ static int tzcompare(const void *m1, const void *m2) UNITTEST int checktz(const char *check, size_t len); UNITTEST int checktz(const char *check, size_t len) { + char first; if(len > 4) return -1; - else if(len == 1) { + first = check[0]; + if(first < 'A') + return -1; + if(len == 1) { /* short-cut single letter names */ - if((check[0] < 'A') || (check[0] > 'Z') || (check[0] == 'J')) + if((first > 'Z') || (first == 'J')) return -1; /* no such tz */ - return tzone[check[0] - 'A'].offset * 60; + return tzone[first - 'A'] * 60; } else if(len == 2) { /* two-letter names */ if(check[1] == 'T') { - if(check[0] == 'N') - return tztwo[0].offset * 60; - else if(check[0] == 'U') - return tztwo[1].offset * 60; + if(first == 'N') + return 11 * 60 * 60; + else if(first == 'U') + return 0; } return -1; /* nope */ } @@ -353,9 +360,9 @@ UNITTEST int checktz(const char *check, size_t len) /* three-letter name */ const struct tzinfo *what; struct tzinfo find; - if((check[0] < 'A') || (check[0] > 'Y')) + if(first > 'Y') return -1; - curlx_strcopy(find.name, sizeof(find.name), check, len); + find.tz = MKTZ(first, check[1], check[2], 0); what = bsearch(&find, tzthree, CURL_ARRAYSIZE(tzthree), sizeof(tzthree[0]), tzcompare); if(what) @@ -365,9 +372,9 @@ UNITTEST int checktz(const char *check, size_t len) /* four-letter name */ const struct tzinfo *what; struct tzinfo find; - if((check[0] < 'A') || (check[0] > 'W')) + if(first > 'W') return -1; - curlx_strcopy(find.name, sizeof(find.name), check, len); + find.tz = MKTZ(first, check[1], check[2], check[3]); what = bsearch(&find, tzfoura, CURL_ARRAYSIZE(tzfoura), sizeof(tzfoura[0]), tzcompare); if(what) From e47b56a762058ed1b3ea4ac8a8d4fdc98abac088 Mon Sep 17 00:00:00 2001 From: Joshua Rogers Date: Sat, 19 Sep 2026 00:26:43 +0200 Subject: [PATCH 175/481] socks: support CURLAUTH_ONLY in CURLOPT_SOCKS5_AUTH The SOCKS5 method negotiation always offered "no authentication" and accepted it, so the bitmask could not express that the connection has to be authenticated. CURLAUTH_NONE is zero and cannot say it either. With CURLAUTH_ONLY set, the method is neither offered nor accepted, and a connection that did not authenticate is not reused. Document both, and add tests 1742, 1743 and 1744. Closes #23003 --- docs/libcurl/opts/CURLOPT_SOCKS5_AUTH.md | 19 ++++++- lib/setopt.c | 11 +++- lib/socks.c | 17 +++++- lib/url.c | 10 ++++ lib/urldata.h | 2 + tests/data/Makefile.am | 3 + tests/data/test1742 | 62 ++++++++++++++++++++ tests/data/test1743 | 46 +++++++++++++++ tests/data/test1744 | 60 ++++++++++++++++++++ tests/libtest/Makefile.inc | 2 + tests/libtest/lib1742.c | 72 ++++++++++++++++++++++++ tests/libtest/lib1744.c | 71 +++++++++++++++++++++++ 12 files changed, 367 insertions(+), 8 deletions(-) create mode 100644 tests/data/test1742 create mode 100644 tests/data/test1743 create mode 100644 tests/data/test1744 create mode 100644 tests/libtest/lib1742.c create mode 100644 tests/libtest/lib1744.c diff --git a/docs/libcurl/opts/CURLOPT_SOCKS5_AUTH.md b/docs/libcurl/opts/CURLOPT_SOCKS5_AUTH.md index 2fe3de3f45d7..a1dcc1db8f02 100644 --- a/docs/libcurl/opts/CURLOPT_SOCKS5_AUTH.md +++ b/docs/libcurl/opts/CURLOPT_SOCKS5_AUTH.md @@ -29,9 +29,20 @@ CURLcode curl_easy_setopt(CURL *handle, CURLOPT_SOCKS5_AUTH, long bitmask); Pass a long as parameter, which is set to a bitmask, to tell libcurl which authentication method(s) are allowed for SOCKS5 proxy authentication. The only supported flags are *CURLAUTH_BASIC*, which allows username/password -authentication, *CURLAUTH_GSSAPI*, which allows GSS-API authentication, and -*CURLAUTH_NONE*, which allows no authentication. Set the actual username and -password with the CURLOPT_PROXYUSERPWD(3) option. +authentication, and *CURLAUTH_GSSAPI*, which allows GSS-API authentication. +Set the actual username and password with the CURLOPT_PROXYUSERPWD(3) option. + +libcurl offers the SOCKS5 "no authentication" method in addition to the ones +in the bitmask, and accepts it if the proxy selects it. *CURLAUTH_NONE* is zero +and therefore cannot be combined with the other flags; setting the bitmask to +*CURLAUTH_NONE* makes libcurl offer only the no authentication method. + +Add *CURLAUTH_ONLY* to the bitmask to require authentication. libcurl then does +not offer the no authentication method, and rejects it with *CURLE_PROXY* if +the proxy selects it anyway. A connection that was set up without SOCKS5 +authentication is also not reused for such a transfer. Setting *CURLAUTH_ONLY* +without *CURLAUTH_BASIC* or *CURLAUTH_GSSAPI* makes curl_easy_setopt(3) return +*CURLE_BAD_FUNCTION_ARGUMENT*. The specific socks authentication method is an *access property*, it does not change the security context. This means that this option changes how the @@ -76,6 +87,8 @@ int main(void) **CURLAUTH_*** macros became `long` types in 7.26.0, prior to this version a `long` cast was necessary when passed to curl_easy_setopt(3). +*CURLAUTH_ONLY* is supported since 8.23.0. + # %AVAILABILITY% # RETURN VALUE diff --git a/lib/setopt.c b/lib/setopt.c index 5e52a6dae676..3cac55678f2e 100644 --- a/lib/setopt.c +++ b/lib/setopt.c @@ -990,11 +990,16 @@ static CURLcode setopt_long_proxy(struct Curl_easy *data, CURLoption option, #endif s->proxytype = (unsigned char)arg; break; - case CURLOPT_SOCKS5_AUTH: - if(arg & ~(CURLAUTH_BASIC | CURLAUTH_GSSAPI)) + case CURLOPT_SOCKS5_AUTH: { + unsigned long auth = (unsigned long)arg; + if(auth & ~(CURLAUTH_BASIC | CURLAUTH_GSSAPI | CURLAUTH_ONLY)) return CURLE_NOT_BUILT_IN; - s->socks5auth = (unsigned char)arg; + if((auth & CURLAUTH_ONLY) && !(auth & (CURLAUTH_BASIC | CURLAUTH_GSSAPI))) + return CURLE_BAD_FUNCTION_ARGUMENT; + s->socks5auth = (unsigned char)(auth & (CURLAUTH_BASIC | CURLAUTH_GSSAPI)); + s->socks5_auth_only = !!(auth & CURLAUTH_ONLY); break; + } default: return CURLE_UNKNOWN_OPTION; } diff --git a/lib/socks.c b/lib/socks.c index 949f20c13539..dfc4cb06b535 100644 --- a/lib/socks.c +++ b/lib/socks.c @@ -587,8 +587,11 @@ static CURLproxycode socks5_req0_init(struct Curl_cfilter *cf, Curl_creds_unlink(&sx->creds); req[0] = 5; /* version */ - nauths = 1; - req[1 + nauths] = 0; /* 1. no authentication */ + nauths = 0; + if(!data->set.socks5_auth_only) { + ++nauths; + req[1 + nauths] = 0; /* no authentication */ + } #if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI) if(auth & CURLAUTH_GSSAPI) { ++nauths; @@ -599,6 +602,10 @@ static CURLproxycode socks5_req0_init(struct Curl_cfilter *cf, ++nauths; req[1 + nauths] = 2; /* username/password */ } + if(!nauths) { + failf(data, "SOCKS5: no acceptable authentication method is available."); + return CURLPX_NO_AUTH; + } req[1] = nauths; req_len = 2 + nauths; @@ -631,11 +638,16 @@ static CURLproxycode socks5_check_resp0(struct socks_ctx *sx, switch(auth_mode) { case 0: + if(data->set.socks5_auth_only) { + failf(data, "SOCKS5 proxy selected no authentication."); + return CURLPX_NO_AUTH; + } /* DONE! No authentication needed. Send request. */ sxstate(sx, cf, data, SOCKS5_ST_REQ1_INIT); return CURLPX_OK; case 1: if(data->set.socks5auth & CURLAUTH_GSSAPI) { + cf->conn->bits.socks5_authenticated = TRUE; sxstate(sx, cf, data, SOCKS5_ST_GSSAPI_INIT); return CURLPX_OK; } @@ -644,6 +656,7 @@ static CURLproxycode socks5_check_resp0(struct socks_ctx *sx, case 2: /* regular name + password authentication */ if(data->set.socks5auth & CURLAUTH_BASIC) { + cf->conn->bits.socks5_authenticated = TRUE; sxstate(sx, cf, data, SOCKS5_ST_AUTH_INIT); return CURLPX_OK; } diff --git a/lib/url.c b/lib/url.c index 60304a5f538e..9026f96d6651 100644 --- a/lib/url.c +++ b/lib/url.c @@ -749,6 +749,16 @@ static bool url_match_proxy_use(struct connectdata *conn, if(!proxy_info_matches(&m->needle->http_proxy, &conn->http_proxy)) return FALSE; + if(m->data->set.socks5_auth_only && !conn->bits.socks5_authenticated && + ((conn->socks_proxy.proxytype == CURLPROXY_SOCKS5) || + (conn->socks_proxy.proxytype == CURLPROXY_SOCKS5_HOSTNAME))) { + DEBUGF(infof(m->data, + "Connection #%" FMT_OFF_T + " was not SOCKS5 authenticated, cannot reuse", + conn->connection_id)); + return FALSE; + } + if(CURL_PROXY_IS_HTTPS(m->needle->http_proxy.proxytype)) { /* https proxies come in different types, http/1.1, h2, ... */ /* match SSL config to proxy */ diff --git a/lib/urldata.h b/lib/urldata.h index 1ff2cbb2a98e..40a31d2ea7db 100644 --- a/lib/urldata.h +++ b/lib/urldata.h @@ -184,6 +184,7 @@ struct ConnectBits { BIT(connect_only); #ifndef CURL_DISABLE_PROXY BIT(origin_is_proxy); /* if set, the connection's origin is a proxy */ + BIT(socks5_authenticated); /* SOCKS5 negotiated an authentication method */ #endif /* always modify bits.close with the connclose() and connkeep() macros! */ BIT(close); /* if set, we close the connection after this request */ @@ -1107,6 +1108,7 @@ struct UserDefined { #if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI) BIT(socks5_gssapi_nec); /* Flag to support NEC SOCKS5 server */ #endif + BIT(socks5_auth_only); /* SOCKS5 must authenticate */ BIT(sasl_ir); /* Enable/disable SASL initial response */ BIT(tcp_keepalive); /* use TCP keepalives */ BIT(tcp_fastopen); /* use TCP Fast Open */ diff --git a/tests/data/Makefile.am b/tests/data/Makefile.am index 8cc9cfc40170..118d2d10de06 100644 --- a/tests/data/Makefile.am +++ b/tests/data/Makefile.am @@ -1734,6 +1734,9 @@ TESTCASES = \ test1725 \ test1740 \ test1741 \ + test1742 \ + test1743 \ + test1744 \ test1790 \ test1791 \ test1800 \ diff --git a/tests/data/test1742 b/tests/data/test1742 new file mode 100644 index 000000000000..fa82f1bde29a --- /dev/null +++ b/tests/data/test1742 @@ -0,0 +1,62 @@ + + + + +HTTP +HTTP GET +SOCKS5 + + + +# Server-side + + +HTTP/1.1 200 OK +Content-Length: 6 +Connection: close + +-foo- + + + +-foo- + + +method 2 +user silly +password person +nmethods_min 1 +nmethods_max 1 + + + +# Client-side + + +proxy + + +http +socks5 + + +lib%TESTNUMBER + + +SOCKS5 CURLAUTH_ONLY does not offer the no authentication method + + +http://%HOSTIP:%HTTPPORT/%TESTNUMBER %HOSTIP:%SOCKSPORT + + + +# Verify data after the test has been "shot" + + +GET /%TESTNUMBER HTTP/1.1 +Host: %HOSTIP:%HTTPPORT +Accept: */* + + + + diff --git a/tests/data/test1743 b/tests/data/test1743 new file mode 100644 index 000000000000..6773b7e2ebcb --- /dev/null +++ b/tests/data/test1743 @@ -0,0 +1,46 @@ + + + + +HTTP +HTTP GET +SOCKS5 + + + +# Server-side + + + + +method 0 + + + +# Client-side + + +proxy + + +http +socks5 + + +lib1742 + + +SOCKS5 CURLAUTH_ONLY rejects a proxy that selects no authentication + + +http://%HOSTIP:%HTTPPORT/%TESTNUMBER %HOSTIP:%SOCKSPORT + + + +# Verify data after the test has been "shot" + + +97 + + + diff --git a/tests/data/test1744 b/tests/data/test1744 new file mode 100644 index 000000000000..777571edb33c --- /dev/null +++ b/tests/data/test1744 @@ -0,0 +1,60 @@ + + + + +HTTP +HTTP GET +SOCKS5 + + + +# Server-side + + +HTTP/1.1 200 OK +Content-Length: 6 + +-foo- + + + +-foo- + + +method 0 + + + +# Client-side + + +proxy + + +http +socks5 + + +lib%TESTNUMBER + + +SOCKS5 CURLAUTH_ONLY does not reuse an unauthenticated connection + + +http://%HOSTIP:%HTTPPORT/%TESTNUMBER %HOSTIP:%SOCKSPORT + + + +# Verify data after the test has been "shot" + + +97 + + +GET /%TESTNUMBER HTTP/1.1 +Host: %HOSTIP:%HTTPPORT +Accept: */* + + + + diff --git a/tests/libtest/Makefile.inc b/tests/libtest/Makefile.inc index 270e6b4d465e..2b85e103e009 100644 --- a/tests/libtest/Makefile.inc +++ b/tests/libtest/Makefile.inc @@ -243,6 +243,8 @@ TESTS_C = \ lib1662.c \ lib1678.c \ lib1686.c \ + lib1742.c \ + lib1744.c \ lib1791.c \ lib1900.c \ lib1901.c \ diff --git a/tests/libtest/lib1742.c b/tests/libtest/lib1742.c new file mode 100644 index 000000000000..318478f8cf8f --- /dev/null +++ b/tests/libtest/lib1742.c @@ -0,0 +1,72 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ +/* + * argv1 = URL + * argv2 = SOCKS5 proxy + */ + +#include "first.h" + +static CURLcode test_lib1742(const char *URL) +{ + CURLcode result = CURLE_OK; + CURL *curl = NULL; + + if(test_argc < 2) + return TEST_ERR_MAJOR_BAD; + + res_global_init(CURL_GLOBAL_ALL); + if(result) + return result; + + curl = curl_easy_init(); + if(!curl) { + curl_mfprintf(stderr, "curl_easy_init() failed\n"); + curl_global_cleanup(); + return TEST_ERR_MAJOR_BAD; + } + + if(curl_easy_setopt(curl, CURLOPT_SOCKS5_AUTH, (long)CURLAUTH_ONLY) != + CURLE_BAD_FUNCTION_ARGUMENT) { + curl_mfprintf(stderr, "CURLAUTH_ONLY without a method was accepted\n"); + result = TEST_ERR_FAILURE; + goto test_cleanup; + } + + easy_setopt(curl, CURLOPT_URL, URL); + easy_setopt(curl, CURLOPT_PROXY, libtest_arg2); + easy_setopt(curl, CURLOPT_PROXYTYPE, (long)CURLPROXY_SOCKS5); + easy_setopt(curl, CURLOPT_PROXYUSERPWD, "silly:person"); + easy_setopt(curl, CURLOPT_SOCKS5_AUTH, + (long)(CURLAUTH_BASIC | CURLAUTH_ONLY)); + easy_setopt(curl, CURLOPT_VERBOSE, 1L); + + result = curl_easy_perform(curl); + +test_cleanup: + + curl_easy_cleanup(curl); + curl_global_cleanup(); + return result; +} diff --git a/tests/libtest/lib1744.c b/tests/libtest/lib1744.c new file mode 100644 index 000000000000..f644aa672a9a --- /dev/null +++ b/tests/libtest/lib1744.c @@ -0,0 +1,71 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ +/* + * argv1 = URL + * argv2 = SOCKS5 proxy + */ + +#include "first.h" + +static CURLcode test_lib1744(const char *URL) +{ + CURLcode result = CURLE_OK; + CURL *curl = NULL; + + if(test_argc < 2) + return TEST_ERR_MAJOR_BAD; + + res_global_init(CURL_GLOBAL_ALL); + if(result) + return result; + + curl = curl_easy_init(); + if(!curl) { + curl_mfprintf(stderr, "curl_easy_init() failed\n"); + curl_global_cleanup(); + return TEST_ERR_MAJOR_BAD; + } + + easy_setopt(curl, CURLOPT_URL, URL); + easy_setopt(curl, CURLOPT_PROXY, libtest_arg2); + easy_setopt(curl, CURLOPT_PROXYTYPE, (long)CURLPROXY_SOCKS5); + easy_setopt(curl, CURLOPT_PROXYUSERPWD, "silly:person"); + easy_setopt(curl, CURLOPT_VERBOSE, 1L); + + /* the proxy asks for no authentication and the connection is kept */ + result = curl_easy_perform(curl); + if(result) + goto test_cleanup; + + /* that connection must not be reused now that auth is required */ + easy_setopt(curl, CURLOPT_SOCKS5_AUTH, + (long)(CURLAUTH_BASIC | CURLAUTH_ONLY)); + result = curl_easy_perform(curl); + +test_cleanup: + + curl_easy_cleanup(curl); + curl_global_cleanup(); + return result; +} From a40991b97c0ce721e85acbe16e16fe79b7b68c51 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 19 Sep 2026 06:12:40 +0000 Subject: [PATCH 176/481] Dockerfile: update debian:bookworm-slim Docker digest to 3783cc0 Closes #23004 --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 934d4b119caf..df7b206dfaf9 100644 --- a/Dockerfile +++ b/Dockerfile @@ -24,7 +24,7 @@ # $ ./scripts/maketgz 8.7.1 # To update, get the latest digest e.g. from https://hub.docker.com/_/debian/tags -FROM debian:bookworm-slim@sha256:88200866dfff7ea7f5cbcb6ec7c8a701889efe6fe859fe64d6990e4b07ea4171 +FROM debian:bookworm-slim@sha256:3783cc01769c7b2b1b83a5c5ad96c815348e28ed7da68e2e3687004faa906251 RUN apt-get update -qq && apt-get install -qq -y --no-install-recommends \ build-essential make autoconf automake libtool git perl zip zlib1g-dev gawk && \ From b148863e7da713c69e1813440732cb2dbed7281b Mon Sep 17 00:00:00 2001 From: Joshua Rogers Date: Fri, 18 Sep 2026 23:53:45 +0200 Subject: [PATCH 177/481] tool_operate: clear the retry flag when the output file fails retrycheck() left *retryp set when the flush/truncate/seek of the partial output file failed, so post_per_transfer() closed the output file and the caller retried the same transfer anyway. The next response was written through the closed FILE*, which was then closed a second time. Clear the flag on those errors and reset the stream after closing it. Add test 1716, with a debug-build hook to make the truncate fail. Closes #23001 --- src/tool_operate.c | 12 +++++++++- tests/data/Makefile.am | 1 + tests/data/test1716 | 54 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 66 insertions(+), 1 deletion(-) create mode 100644 tests/data/test1716 diff --git a/src/tool_operate.c b/src/tool_operate.c index 8256dfa14588..0d689455c381 100644 --- a/src/tool_operate.c +++ b/src/tool_operate.c @@ -547,6 +547,7 @@ static CURLcode retrycheck(struct OperationConfig *config, notef("Keeping %" CURL_FORMAT_CURL_OFF_T " bytes", outs->bytes); if(fflush(outs->stream)) { errorf("Failed to flush output file stream"); + *retryp = FALSE; return CURLE_WRITE_ERROR; } if(outs->bytes >= CURL_OFF_T_MAX - outs->init) { @@ -554,6 +555,7 @@ static CURLcode retrycheck(struct OperationConfig *config, "%" CURL_FORMAT_CURL_OFF_T " + " "%" CURL_FORMAT_CURL_OFF_T ")", outs->init, outs->bytes); + *retryp = FALSE; return CURLE_WRITE_ERROR; } truncate = FALSE; @@ -577,10 +579,15 @@ static CURLcode retrycheck(struct OperationConfig *config, notef("Throwing away %" CURL_FORMAT_CURL_OFF_T " bytes", outs->bytes); /* truncate file at the position where we started appending */ - if(toolx_ftruncate(fileno(outs->stream), outs->init)) { + if( +#ifdef DEBUGBUILD + getenv("CURL_DBG_TRUNCATE_FAIL") || +#endif + toolx_ftruncate(fileno(outs->stream), outs->init)) { /* when truncate fails, we cannot append as then we create something strange, bail out */ errorf("Failed to truncate file"); + *retryp = FALSE; return CURLE_WRITE_ERROR; } /* now seek to the end of the file, the position where we @@ -589,6 +596,7 @@ static CURLcode retrycheck(struct OperationConfig *config, if(rc) { errorf("Failed seeking to end of file"); + *retryp = FALSE; return CURLE_WRITE_ERROR; } outs->bytes = 0; /* clear for next round */ @@ -693,6 +701,8 @@ static CURLcode post_close_output(struct per_transfer *per, /* Close the outs file */ if(outs->fopened && outs->stream) { rc = curlx_fclose(outs->stream); + outs->stream = NULL; + outs->fopened = FALSE; if(!result && rc) { /* something went wrong in the writing process */ result = CURLE_WRITE_ERROR; diff --git a/tests/data/Makefile.am b/tests/data/Makefile.am index 118d2d10de06..2b34cba6bc0b 100644 --- a/tests/data/Makefile.am +++ b/tests/data/Makefile.am @@ -1726,6 +1726,7 @@ TESTCASES = \ test1713 \ test1714 \ test1715 \ + test1716 \ test1720 \ test1721 \ test1722 \ diff --git a/tests/data/test1716 b/tests/data/test1716 new file mode 100644 index 000000000000..fad97f7037be --- /dev/null +++ b/tests/data/test1716 @@ -0,0 +1,54 @@ + + + + +HTTP +HTTP GET +retry + + + +# Server-side + + +HTTP/1.1 503 Service Unavailable +Content-Length: 6 +Connection: close + +-foo- + + + +# Client-side + + +http + + +Debug + + +CURL_DBG_TRUNCATE_FAIL=1 + + +--retry when the output file cannot be truncated + + +http://%HOSTIP:%HTTPPORT/%TESTNUMBER --retry 2 --retry-delay 0 + + + +# Verify data after the test has been "shot" + + +23 + + +GET /%TESTNUMBER HTTP/1.1 +Host: %HOSTIP:%HTTPPORT +User-Agent: curl/%VERSION +Accept: */* + + + + From 39088cd295592ca6d4c5e687c75b400a67a87906 Mon Sep 17 00:00:00 2001 From: Jorge Rocamora <33847633+aeroyorch@users.noreply.github.com> Date: Sat, 19 Sep 2026 09:05:26 +0200 Subject: [PATCH 178/481] vssh: do not busy-loop in blocking state machine without a timeout Closes #23005 --- lib/vssh/libssh.c | 3 ++- lib/vssh/libssh2.c | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/vssh/libssh.c b/lib/vssh/libssh.c index 6d2a1e2d24cc..276c7d225924 100644 --- a/lib/vssh/libssh.c +++ b/lib/vssh/libssh.c @@ -2500,7 +2500,8 @@ static CURLcode myssh_block_statemach(struct Curl_easy *data, if(block) { curl_socket_t fd_read = conn->sock[FIRSTSOCKET]; /* wait for the socket to become ready */ - (void)SOCKET_READABLE(fd_read, left_ms > 1000 ? 1000 : left_ms); + (void)SOCKET_READABLE(fd_read, + (!left_ms || left_ms > 1000) ? 1000 : left_ms); } } diff --git a/lib/vssh/libssh2.c b/lib/vssh/libssh2.c index b950b88652ec..1a05eaa14461 100644 --- a/lib/vssh/libssh2.c +++ b/lib/vssh/libssh2.c @@ -3239,7 +3239,7 @@ static CURLcode ssh_block_statemach(struct Curl_easy *data, fd_write = sock; /* wait for the socket to become ready */ (void)Curl_socket_check(fd_read, CURL_SOCKET_BAD, fd_write, - left_ms > 1000 ? 1000 : left_ms); + (!left_ms || left_ms > 1000) ? 1000 : left_ms); } } From 4f888df0c407f7251de6e062f91c54b40b1c7e4f Mon Sep 17 00:00:00 2001 From: Joshua Rogers Date: Sat, 19 Sep 2026 00:11:38 +0200 Subject: [PATCH 179/481] http: ignore Proxy-Authenticate unless the response comes from a proxy A 407 response was parsed for proxy authentication without checking where it came from, so a host reached directly, or an origin reached through a CONNECT tunnel, could set the proxy auth state. A later request to the proxy then sent a Proxy-Authorization built from the realm and nonce of that host, before the proxy challenged. The proxy peer stays set for tunnels, so gate on origin_is_proxy, the same condition Curl_http_output_auth() uses to emit these headers. Also flush the Digest state when it has no origin. Add test 1616. Closes #23002 --- lib/http.c | 11 +++- lib/http_digest.c | 5 +- tests/data/Makefile.am | 1 + tests/data/test1616 | 108 +++++++++++++++++++++++++++++++++++++ tests/libtest/Makefile.inc | 1 + tests/libtest/lib1616.c | 104 +++++++++++++++++++++++++++++++++++ 6 files changed, 226 insertions(+), 4 deletions(-) create mode 100644 tests/data/test1616 create mode 100644 tests/libtest/lib1616.c diff --git a/lib/http.c b/lib/http.c index 3ed023753d21..3b3c542c90fd 100644 --- a/lib/http.c +++ b/lib/http.c @@ -3465,8 +3465,15 @@ static CURLcode http_header_p(struct Curl_easy *data, } #endif if((407 == k->httpcode) && HD_IS(hd, hdlen, "Proxy-authenticate:")) { - char *auth = Curl_copy_header_value(hd); - CURLcode result = auth ? CURLE_OK : CURLE_OUT_OF_MEMORY; + char *auth; + CURLcode result; +#ifndef CURL_DISABLE_PROXY + if(!data->conn->bits.origin_is_proxy) + /* this response is not from a forward proxy, not for us */ + return CURLE_OK; +#endif + auth = Curl_copy_header_value(hd); + result = auth ? CURLE_OK : CURLE_OUT_OF_MEMORY; if(!result) { result = Curl_http_input_auth(data, TRUE, auth); curlx_free(auth); diff --git a/lib/http_digest.c b/lib/http_digest.c index 4968e27c331f..e739b2be801c 100644 --- a/lib/http_digest.c +++ b/lib/http_digest.c @@ -41,9 +41,10 @@ static void digest_flush_stale(struct Curl_easy *data, struct Curl_creds *creds) { bool flush = FALSE; - if(digest->origin && !Curl_peer_equal(peer, digest->origin)) { + if(!digest->origin || !Curl_peer_equal(peer, digest->origin)) { CURL_TRC_M(data, "http_digest, reset on peer change to %s:%u", - peer->hostname, peer->port); + peer ? peer->hostname : "-", + (unsigned int)(peer ? peer->port : 0)); flush = TRUE; } else if(digest->creds && !Curl_creds_same(creds, digest->creds)) { diff --git a/tests/data/Makefile.am b/tests/data/Makefile.am index 2b34cba6bc0b..e3735f94530a 100644 --- a/tests/data/Makefile.am +++ b/tests/data/Makefile.am @@ -1634,6 +1634,7 @@ TESTCASES = \ test1613 \ test1614 \ test1615 \ + test1616 \ test1617 \ test1618 \ test1619 \ diff --git a/tests/data/test1616 b/tests/data/test1616 new file mode 100644 index 000000000000..114501b32aa2 --- /dev/null +++ b/tests/data/test1616 @@ -0,0 +1,108 @@ + + + + +HTTP +HTTP GET +HTTP proxy +HTTP proxy Digest auth + + + +# Server-side + + +HTTP/1.1 200 OK +Content-Length: 12 +Connection: close + +proxied one + + + +HTTP/1.1 407 Proxy Authentication Required +Proxy-Authenticate: Digest realm="attacker", nonce="1053604598" +Content-Length: 7 +Connection: close + +direct + + + +HTTP/1.1 407 Proxy Authentication Required +Proxy-Authenticate: Digest realm="tunnel-attacker", nonce="1053604598" +Content-Length: 9 +Connection: close + +tunneled + + + +HTTP/1.1 200 Mighty fine indeed + + + + +proxied one +direct +proxied one +tunneled +proxied one + + + +# Client-side + + +http + + +lib%TESTNUMBER + + +!SSPI +crypto +proxy +digest + + +HTTP 407 from a non-proxied host must not set proxy Digest state + + +http://test.remote.example.com/%TESTNUMBER0001 %HOSTIP %HTTPPORT %TESTNUMBER + + + +# Verify data after the test has been "shot" + + +GET http://test.remote.example.com/%TESTNUMBER0001 HTTP/1.1 +Host: test.remote.example.com +Accept: */* +Proxy-Connection: Keep-Alive + +GET /%TESTNUMBER0002 HTTP/1.1 +Host: %HOSTIP:%HTTPPORT +Accept: */* + +GET http://test.remote.example.com/%TESTNUMBER0001 HTTP/1.1 +Host: test.remote.example.com +Accept: */* +Proxy-Connection: Keep-Alive + +CONNECT test.remote.example.com:%HTTPPORT HTTP/1.1 +Host: test.remote.example.com:%HTTPPORT +Proxy-Connection: Keep-Alive + +GET /%TESTNUMBER0004 HTTP/1.1 +Host: test.remote.example.com:%HTTPPORT +Accept: */* + +GET http://test.remote.example.com/%TESTNUMBER0001 HTTP/1.1 +Host: test.remote.example.com +Accept: */* +Proxy-Connection: Keep-Alive + + + + diff --git a/tests/libtest/Makefile.inc b/tests/libtest/Makefile.inc index 2b85e103e009..ad0057bd58aa 100644 --- a/tests/libtest/Makefile.inc +++ b/tests/libtest/Makefile.inc @@ -237,6 +237,7 @@ TESTS_C = \ lib1598.c \ lib1599.c \ lib1605.c \ + lib1616.c \ lib1647.c \ lib1648.c \ lib1649.c \ diff --git a/tests/libtest/lib1616.c b/tests/libtest/lib1616.c new file mode 100644 index 000000000000..328869b52f3b --- /dev/null +++ b/tests/libtest/lib1616.c @@ -0,0 +1,104 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ +/* + * argv1 = URL fetched through the proxy + * argv2 = proxy host + * argv3 = proxy port + * argv4 = test number + */ + +#include "first.h" + +static CURLcode test_lib1616(const char *URL) +{ + CURLcode result = CURLE_OK; + CURL *curl = NULL; + char proxy[128]; + char direct[256]; + char tunneled[256]; + + if(test_argc < 4) + return TEST_ERR_MAJOR_BAD; + + curl_msnprintf(proxy, sizeof(proxy), "http://%s:%s", + libtest_arg2, libtest_arg3); + curl_msnprintf(direct, sizeof(direct), "http://%s:%s/%s0002", + libtest_arg2, libtest_arg3, libtest_arg4); + curl_msnprintf(tunneled, sizeof(tunneled), + "http://test.remote.example.com:%s/%s0004", + libtest_arg3, libtest_arg4); + + res_global_init(CURL_GLOBAL_ALL); + if(result) + return result; + + curl = curl_easy_init(); + if(!curl) { + curl_mfprintf(stderr, "curl_easy_init() failed\n"); + curl_global_cleanup(); + return TEST_ERR_MAJOR_BAD; + } + + start_test_timing(); + + easy_setopt(curl, CURLOPT_PROXY, proxy); + easy_setopt(curl, CURLOPT_NOPROXY, libtest_arg2); + easy_setopt(curl, CURLOPT_PROXYAUTH, (long)CURLAUTH_DIGEST); + easy_setopt(curl, CURLOPT_PROXYUSERPWD, "silly:person"); + + easy_setopt(curl, CURLOPT_URL, URL); + result = curl_easy_perform(curl); + if(result) + goto test_cleanup; + + /* the excluded host is reached directly and answers 407 */ + easy_setopt(curl, CURLOPT_URL, direct); + result = curl_easy_perform(curl); + if(result) + goto test_cleanup; + + /* the same proxy as before must not get a Proxy-Authorization header */ + easy_setopt(curl, CURLOPT_URL, URL); + result = curl_easy_perform(curl); + if(result) + goto test_cleanup; + + /* an origin reached through a CONNECT tunnel answers 407 as well */ + easy_setopt(curl, CURLOPT_HTTPPROXYTUNNEL, 1L); + easy_setopt(curl, CURLOPT_URL, tunneled); + result = curl_easy_perform(curl); + if(result) + goto test_cleanup; + + /* and that must not reach the proxy either */ + easy_setopt(curl, CURLOPT_HTTPPROXYTUNNEL, 0L); + easy_setopt(curl, CURLOPT_URL, URL); + result = curl_easy_perform(curl); + +test_cleanup: + + curl_easy_cleanup(curl); + curl_global_cleanup(); + return result; +} From 370367fcd5116c7e9fc565a99e24aa47929cf43d Mon Sep 17 00:00:00 2001 From: minnnjuuu Date: Mon, 21 Sep 2026 03:17:07 +0900 Subject: [PATCH 180/481] examples: clean up crawler link parsing resources Release all libxml objects on exit paths. Enforce the configured link limit before adding a new handle. Closes #23008 --- docs/examples/crawler.c | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/docs/examples/crawler.c b/docs/examples/crawler.c index 595375614eb2..46f4ebb531ed 100644 --- a/docs/examples/crawler.c +++ b/docs/examples/crawler.c @@ -136,13 +136,20 @@ static size_t follow_links(CURLM *multi, struct memory *mem, const char *url) return 0; xpath = (xmlChar *)"//a/@href"; context = xmlXPathNewContext(doc); + if(!context) { + xmlFreeDoc(doc); + return 0; + } object = xmlXPathEvalExpression(xpath, context); xmlXPathFreeContext(context); - if(!object) + if(!object) { + xmlFreeDoc(doc); return 0; + } nodeset = object->nodesetval; if(xmlXPathNodeSetIsEmpty(nodeset)) { xmlXPathFreeObject(object); + xmlFreeDoc(doc); return 0; } count = 0; @@ -158,16 +165,22 @@ static size_t follow_links(CURLM *multi, struct memory *mem, const char *url) xmlFree(orig); } link = (char *)href; - if(!link || strlen(link) < 20) + if(!link || strlen(link) < 20) { + xmlFree(link); continue; + } if(!strncmp(link, "http://", 7) || !strncmp(link, "https://", 8)) { - curl_multi_add_handle(multi, make_handle(link)); - if(count++ == max_link_per_page) + if(count >= max_link_per_page) { + xmlFree(link); break; + } + curl_multi_add_handle(multi, make_handle(link)); + count++; } xmlFree(link); } xmlXPathFreeObject(object); + xmlFreeDoc(doc); return count; } From 23952f7f80152f2d176a8666cb276682c2a84bd7 Mon Sep 17 00:00:00 2001 From: Max Dymond Date: Sun, 20 Sep 2026 16:14:18 +0100 Subject: [PATCH 181/481] mqtt: drain queued output before advancing the state machine A successful send can leave unsent bytes queued. Poll for writable events and drain those bytes before processing another packet. Wait for PUBLISH to drain before sending DISCONNECT, and for DISCONNECT to drain before completing the transfer. Separate sending new packets from flushing queued bytes. Add unit coverage for partial, blocked and failed sends, plus MQTT/MQTTS integration tests. Update the test broker to handle fragmented reads. Reported-by: OSS-Fuzz Bug: https://issues.oss-fuzz.com/issues/563679840, https://issues.oss-fuzz.com/issues/559825608 Closes #23007 --- lib/mqtt.c | 89 ++++++++++----- tests/data/Makefile.am | 5 + tests/data/test1693 | 19 ++++ tests/data/test2209 | 46 ++++++++ tests/data/test2210 | 49 +++++++++ tests/data/test2211 | 48 ++++++++ tests/data/test2212 | 51 +++++++++ tests/server/mqttd.c | 52 +++++---- tests/unit/Makefile.inc | 1 + tests/unit/unit1693.c | 236 ++++++++++++++++++++++++++++++++++++++++ 10 files changed, 546 insertions(+), 50 deletions(-) create mode 100644 tests/data/test1693 create mode 100644 tests/data/test2209 create mode 100644 tests/data/test2210 create mode 100644 tests/data/test2211 create mode 100644 tests/data/test2212 create mode 100644 tests/unit/unit1693.c diff --git a/lib/mqtt.c b/lib/mqtt.c index f493ca3d0dfd..752ad370c013 100644 --- a/lib/mqtt.c +++ b/lib/mqtt.c @@ -67,8 +67,10 @@ enum mqttstate { MQTT_SUBACK_COMING, /* 4 - the SUBACK remainder */ MQTT_PUBWAIT, /* 5 - wait for publish */ MQTT_PUB_REMAIN, /* 6 - wait for the remainder of the publish */ + MQTT_POST_DRAIN, /* 7 - finish sending PUBLISH */ + MQTT_DISCONNECT_DRAIN, /* 8 - finish sending DISCONNECT */ - MQTT_NOSTATE /* 7 - never used an actual state */ + MQTT_NOSTATE /* 9 - never used an actual state */ }; struct mqtt_conn { @@ -140,32 +142,48 @@ static CURLcode mqtt_send(struct Curl_easy *data, if(!mq) return CURLE_FAILED_INIT; + /* A new packet must not replace a previously queued packet tail. */ + DEBUGASSERT(!curlx_dyn_len(&mq->sendbuf)); result = Curl_xfer_send(data, buf, len, FALSE, &n); if(result) return result; mq->lastTime = *Curl_pgrs_now(data); Curl_debug(data, CURLINFO_HEADER_OUT, buf, n); - if(len != n) { - size_t nsend = len - n; - if(curlx_dyn_len(&mq->sendbuf)) { - DEBUGASSERT(curlx_dyn_len(&mq->sendbuf) >= nsend); - result = curlx_dyn_tail(&mq->sendbuf, nsend); /* keep this much */ - } - else { - result = curlx_dyn_addn(&mq->sendbuf, &buf[n], nsend); - } - } - else - curlx_dyn_reset(&mq->sendbuf); + if(len != n) + result = curlx_dyn_addn(&mq->sendbuf, &buf[n], len - n); return result; } +/* Send the queued tail, preserving any bytes that still cannot be sent. */ +static CURLcode mqtt_flush(struct Curl_easy *data) +{ + struct MQTT *mq = Curl_meta_get(data, CURL_META_MQTT_EASY); + size_t len, n; + CURLcode result; + + if(!mq) + return CURLE_FAILED_INIT; + len = curlx_dyn_len(&mq->sendbuf); + if(!len) + return CURLE_OK; + + result = Curl_xfer_send(data, curlx_dyn_ptr(&mq->sendbuf), len, FALSE, &n); + if(result) + return result; + mq->lastTime = *Curl_pgrs_now(data); + Curl_debug(data, CURLINFO_HEADER_OUT, curlx_dyn_ptr(&mq->sendbuf), n); + return curlx_dyn_tail(&mq->sendbuf, len - n); +} + /* Generic function called by the multi interface to figure out what socket(s) to wait for and for what actions during the DOING and PROTOCONNECT states */ static CURLcode mqtt_pollset(struct Curl_easy *data, struct easy_pollset *ps) { + struct MQTT *mq = Curl_meta_get(data, CURL_META_MQTT_EASY); + if(mq && curlx_dyn_len(&mq->sendbuf)) + return Curl_pollset_add_out(data, ps, data->conn->sock[FIRSTSOCKET]); return Curl_pollset_add_in(data, ps, data->conn->sock[FIRSTSOCKET]); } @@ -637,6 +655,8 @@ static const char * const statenames[] = { "MQTT_SUBACK_COMING", "MQTT_PUBWAIT", "MQTT_PUB_REMAIN", + "MQTT_POST_DRAIN", + "MQTT_DISCONNECT_DRAIN", "NOT A STATE" }; @@ -843,14 +863,16 @@ static CURLcode mqtt_doing(struct Curl_easy *data, bool *done) if(curlx_dyn_len(&mq->sendbuf)) { /* send the remainder of an outgoing packet */ - result = mqtt_send(data, curlx_dyn_ptr(&mq->sendbuf), - curlx_dyn_len(&mq->sendbuf)); - if(result) + result = mqtt_flush(data); + /* CURLE_OK can still mean a short write. Wait for writable progress + before sending another packet or processing a reply. */ + if(result || curlx_dyn_len(&mq->sendbuf)) return result; } result = mqtt_ping(data); - if(result) + /* Packet processing may send more output, so finish PINGREQ first. */ + if(result || curlx_dyn_len(&mq->sendbuf)) return result; infof(data, "mqtt_doing: state [%d]", (int)mqtt->state); @@ -935,20 +957,31 @@ static CURLcode mqtt_doing(struct Curl_easy *data, bool *done) if(result) break; - if(data->state.httpreq == HTTPREQ_POST) { - result = mqtt_publish(data); - if(!result) { - result = mqtt_disconnect(data); - *done = TRUE; - } - mqtt->nextstate = MQTT_FIRST; - } - else { + if(data->state.httpreq != HTTPREQ_POST) { result = mqtt_subscribe(data); - if(!result) { + if(!result) mqstate(data, MQTT_FIRST, MQTT_SUBACK); - } + break; } + + result = mqtt_publish(data); + if(result) + break; + mqstate(data, MQTT_POST_DRAIN, MQTT_NOSTATE); + FALLTHROUGH(); + case MQTT_POST_DRAIN: + /* PUBLISH must be complete before DISCONNECT can use the send buffer. */ + if(curlx_dyn_len(&mq->sendbuf)) + break; + result = mqtt_disconnect(data); + if(result) + break; + mqstate(data, MQTT_DISCONNECT_DRAIN, MQTT_NOSTATE); + FALLTHROUGH(); + case MQTT_DISCONNECT_DRAIN: + /* Completing now would discard any DISCONNECT bytes still queued. */ + if(!curlx_dyn_len(&mq->sendbuf)) + *done = TRUE; break; case MQTT_SUBACK: diff --git a/tests/data/Makefile.am b/tests/data/Makefile.am index e3735f94530a..9714226438e6 100644 --- a/tests/data/Makefile.am +++ b/tests/data/Makefile.am @@ -1711,6 +1711,7 @@ TESTCASES = \ test1690 \ test1691 \ test1692 \ + test1693 \ \ test1700 \ test1702 \ @@ -1937,6 +1938,10 @@ TESTCASES = \ test2206 \ test2207 \ test2208 \ + test2209 \ + test2210 \ + test2211 \ + test2212 \ \ test2270 \ test2271 \ diff --git a/tests/data/test1693 b/tests/data/test1693 new file mode 100644 index 000000000000..c005ce9e6167 --- /dev/null +++ b/tests/data/test1693 @@ -0,0 +1,19 @@ + + + + +unittest +MQTT + + + + + +unittest +mqtt + + +MQTT partial sends, would-block, polling and packet ordering + + + diff --git a/tests/data/test2209 b/tests/data/test2209 new file mode 100644 index 000000000000..b4842c0c4651 --- /dev/null +++ b/tests/data/test2209 @@ -0,0 +1,46 @@ + + + + +MQTT +MQTT PUBLISH + + + + + + + + + + +mqtt +Debug + + +mqtt + + +MQTT PUBLISH with partial writes + + +CURL_DBG_SOCK_WPARTIAL=50 + + +mqtt://%HOSTIP:%MQTTPORT/%TESTNUMBER -d something --max-time 10 + + + + +# Strip the random part of the client ID. + +s/^(.* 00044d5154540402003c000c6375726c).*/$1/ + + +client CONNECT 18 00044d5154540402003c000c6375726c +server CONNACK 2 20020000 +client PUBLISH f 000432323039736f6d657468696e67 +client DISCONNECT 0 e000 + + + diff --git a/tests/data/test2210 b/tests/data/test2210 new file mode 100644 index 000000000000..9f3d309aeff6 --- /dev/null +++ b/tests/data/test2210 @@ -0,0 +1,49 @@ + + + + +MQTT +MQTT SUBSCRIBE + + + + + +hello + + + + + +mqtt +Debug + + +mqtt + + +MQTT SUBSCRIBE with partial writes + + +CURL_DBG_SOCK_WPARTIAL=50 + + +mqtt://%HOSTIP:%MQTTPORT/%TESTNUMBER --max-time 10 + + + + +# Strip the random part of the client ID. + +s/^(.* 00044d5154540402003c000c6375726c).*/$1/ + + +client CONNECT 18 00044d5154540402003c000c6375726c +server CONNACK 2 20020000 +client SUBSCRIBE 9 000100043232313000 +server SUBACK 3 9003000100 +server PUBLISH c 300c00043232313068656c6c6f0a +server DISCONNECT 0 e000 + + + diff --git a/tests/data/test2211 b/tests/data/test2211 new file mode 100644 index 000000000000..a06d673e0677 --- /dev/null +++ b/tests/data/test2211 @@ -0,0 +1,48 @@ + + + + +MQTT +MQTT PUBLISH +MQTTS + + + + + + + + + + +mqtts +Debug + + +mqtts + + +MQTTS PUBLISH with partial writes + + +# Split MQTT packets, including DISCONNECT, without fragmenting TLS handshakes. +CURL_SMALLSENDS=1 + + +mqtts://%HOSTIP:%MQTTSPORT/%TESTNUMBER -d something --insecure --max-time 10 + + + + +# Strip the random part of the client ID. + +s/^(.* 00044d5154540402003c000c6375726c).*/$1/ + + +client CONNECT 18 00044d5154540402003c000c6375726c +server CONNACK 2 20020000 +client PUBLISH f 000432323131736f6d657468696e67 +client DISCONNECT 0 e000 + + + diff --git a/tests/data/test2212 b/tests/data/test2212 new file mode 100644 index 000000000000..907927780b19 --- /dev/null +++ b/tests/data/test2212 @@ -0,0 +1,51 @@ + + + + +MQTT +MQTT SUBSCRIBE +MQTTS + + + + + +hello + + + + + +mqtts +Debug + + +mqtts + + +MQTTS SUBSCRIBE with partial writes + + +# Split MQTT packets without fragmenting TLS handshakes. +CURL_SMALLSENDS=1 + + +mqtts://%HOSTIP:%MQTTSPORT/%TESTNUMBER --insecure --max-time 10 + + + + +# Strip the random part of the client ID. + +s/^(.* 00044d5154540402003c000c6375726c).*/$1/ + + +client CONNECT 18 00044d5154540402003c000c6375726c +server CONNACK 2 20020000 +client SUBSCRIBE 9 000100043232313200 +server SUBACK 3 9003000100 +server PUBLISH c 300c00043232313268656c6c6f0a +server DISCONNECT 0 e000 + + + diff --git a/tests/server/mqttd.c b/tests/server/mqttd.c index 766b86ca8a32..74bebb8efb2a 100644 --- a/tests/server/mqttd.c +++ b/tests/server/mqttd.c @@ -413,6 +413,25 @@ static int publish(FILE *dump, static char topic[MAX_TOPIC_LENGTH + 1]; +static bool readall(curl_socket_t fd, unsigned char *buffer, size_t len) +{ + size_t nread = 0; + + while(nread < len) { + ssize_t rc = sread(fd, buffer + nread, len - nread); + if(rc <= 0) { + if(rc < 0 && SOCKERRNO == SOCKEINTR && !got_exit_signal) + continue; + logmsg("READ %zd bytes [SHORT!]", rc); + return FALSE; + } + logmsg("READ %zd bytes", rc); + loghex(buffer + nread, rc); + nread += (size_t)rc; + } + return TRUE; +} + static bool fixedheader(curl_socket_t fd, unsigned char *bytep, size_t *remaining_lengthp, @@ -421,23 +440,18 @@ static bool fixedheader(curl_socket_t fd, /* get the fixed header */ unsigned char buffer[10]; - /* get the first two bytes */ - ssize_t rc = sread(fd, buffer, 2); size_t i; - if(rc < 2) { - logmsg("READ %zd bytes [SHORT!]", rc); + + /* get the first two bytes */ + if(!readall(fd, buffer, 2)) return FALSE; /* fail */ - } - logmsg("READ %zd bytes", rc); - loghex(buffer, rc); *bytep = buffer[0]; /* if the length byte has the top bit set, get the next one too */ i = 1; while(buffer[i] & 0x80) { i++; - rc = sread(fd, &buffer[i], 1); - if(rc != 1) { + if(!readall(fd, &buffer[i], 1)) { logmsg("Remaining Length broken"); return FALSE; } @@ -513,14 +527,10 @@ static curl_socket_t mqttit(curl_socket_t fd) buffer = newbuffer; } - if(remaining_length) { - /* reading variable header and payload into buffer */ - rc = sread(fd, buffer, remaining_length); - if(rc > 0) { - logmsg("READ %zd bytes", rc); - loghex(buffer, rc); - } - } + /* Read the complete variable header and payload. */ + if(!readall(fd, buffer, remaining_length)) + goto end; + rc = (ssize_t)remaining_length; if(byte == MQTT_MSG_CONNECT) { logprotocol(FROM_CLIENT, "CONNECT", remaining_length, dump, buffer, rc); @@ -658,11 +668,9 @@ static curl_socket_t mqttit(curl_socket_t fd) #endif /* expect a disconnect here */ /* get the request */ - rc = sread(fd, &buffer[0], 2); - - logmsg("READ %zd bytes [DISCONNECT]", rc); - loghex(buffer, rc); - logprotocol(FROM_CLIENT, "DISCONNECT", 0, dump, buffer, rc); + if(!readall(fd, buffer, 2)) + goto end; + logprotocol(FROM_CLIENT, "DISCONNECT", 0, dump, buffer, 2); goto end; } else { diff --git a/tests/unit/Makefile.inc b/tests/unit/Makefile.inc index af7672004620..897bfe6d8f9e 100644 --- a/tests/unit/Makefile.inc +++ b/tests/unit/Makefile.inc @@ -80,6 +80,7 @@ TESTS_C = \ unit1674.c \ unit1675.c \ unit1676.c \ + unit1693.c \ unit1961.c \ unit1979.c \ unit1980.c \ diff --git a/tests/unit/unit1693.c b/tests/unit/unit1693.c new file mode 100644 index 000000000000..f8bbb811cbde --- /dev/null +++ b/tests/unit/unit1693.c @@ -0,0 +1,236 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ +#include "unitcheck.h" +#include "urldata.h" +#include "mqtt.h" +#include "select.h" +#include "url.h" + +#ifndef CURL_DISABLE_MQTT + +enum { + T1693_CONNECT_LEN = 26, + T1693_PUBLISH_LEN = 15, + T1693_DISCONNECT_LEN = 2, + T1693_PUBLISH_END = T1693_CONNECT_LEN + T1693_PUBLISH_LEN, + T1693_WIRE_LEN = T1693_PUBLISH_END + T1693_DISCONNECT_LEN +}; + +struct t1693_send { + size_t n; + CURLcode result; +}; + +struct t1693_ctx { + const struct t1693_send *sends; + size_t nsends; + size_t send_index; + size_t recv_index; + size_t written; + size_t pending; + unsigned char expected[T1693_WIRE_LEN]; + unsigned char output[T1693_WIRE_LEN]; +}; + +static CURLcode t1693_send(struct Curl_easy *data, int8_t sockindex, + const uint8_t *buf, size_t len, bool eos, + size_t *pnwritten) +{ + struct t1693_ctx *ctx = data->set.private_data; + const struct t1693_send *send; + size_t remaining; + (void)sockindex; + (void)eos; + *pnwritten = 0; + + abort_unless(ctx->send_index < ctx->nsends, "unexpected send"); + send = &ctx->sends[ctx->send_index++]; + if(ctx->send_index == 1) { + /* The CONNECT client ID is random. Save the packet for retry checks. */ + abort_unless(len == T1693_CONNECT_LEN, "unexpected CONNECT size"); + memcpy(ctx->expected, buf, len); + fail_unless(buf[0] == 0x10 && buf[1] == 0x18, + "unexpected CONNECT header"); + } + + if(ctx->written < T1693_CONNECT_LEN) + remaining = T1693_CONNECT_LEN - ctx->written; + else if(ctx->written < T1693_PUBLISH_END) + remaining = T1693_PUBLISH_END - ctx->written; + else + remaining = sizeof(ctx->expected) - ctx->written; + abort_unless(len == remaining, "packet sent before previous packet drained"); + fail_unless(!memcmp(buf, ctx->expected + ctx->written, len), + "queued packet bytes changed"); + ctx->pending = len; + if(send->result) + return send->result; + + abort_unless(send->n <= len, "send script exceeds packet size"); + memcpy(ctx->output + ctx->written, buf, send->n); + ctx->written += send->n; + ctx->pending -= send->n; + *pnwritten = send->n; + return CURLE_OK; + +unit_test_abort: + return CURLE_SEND_ERROR; +} + +static CURLcode t1693_recv(struct Curl_easy *data, int8_t sockindex, + char *buf, size_t len, size_t *pnread) +{ + static const unsigned char connack[] = { 0x20, 0x02, 0x00, 0x00 }; + struct t1693_ctx *ctx = data->set.private_data; + size_t n = sizeof(connack) - ctx->recv_index; + (void)sockindex; + *pnread = 0; + abort_unless(ctx->written == T1693_CONNECT_LEN, + "read while an outgoing packet is queued"); + abort_unless(n, "unexpected read after CONNACK"); + if(n > len) + n = len; + memcpy(buf, connack + ctx->recv_index, n); + ctx->recv_index += n; + *pnread = n; + return CURLE_OK; + +unit_test_abort: + return CURLE_RECV_ERROR; +} + +static void t1693_meta_dtor(void *p) +{ + /* MQTT metadata supplies its own destructor. */ + (void)p; +} + +static void t1693_run(const struct t1693_send *sends, size_t nsends, + CURLcode expected_result) +{ + static const unsigned char post[] = { + 0x30, 0x0d, 0x00, 0x04, 't', 'e', 's', 't', + 'p', 'a', 'y', 'l', 'o', 'a', 'd', 0xe0, 0x00 + }; + struct t1693_ctx ctx; + struct Curl_easy *data = NULL; + struct connectdata *conn = NULL; + struct easy_pollset ps; + CURLcode result; + bool done = FALSE; + size_t i; + + Curl_pollset_init(&ps); + memset(&ctx, 0, sizeof(ctx)); + ctx.sends = sends; + ctx.nsends = nsends; + memcpy(ctx.expected + T1693_CONNECT_LEN, post, sizeof(post)); + + data = curl_easy_init(); + abort_unless(data, "easy handle allocation failed"); + conn = curlx_calloc(1, sizeof(*conn)); + abort_unless(conn, "connection allocation failed"); + Curl_hash_init(&conn->meta_hash, 23, CURL_HASH_TYPE_BYTES, t1693_meta_dtor); + data->conn = conn; + conn->sock[FIRSTSOCKET] = 42; + conn->sock[SECONDARYSOCKET] = CURL_SOCKET_BAD; + conn->send[FIRSTSOCKET] = t1693_send; + conn->recv[FIRSTSOCKET] = t1693_recv; + data->set.private_data = &ctx; + data->set.postfields = CURL_UNCONST("payload"); + data->state.httpreq = HTTPREQ_POST; + data->state.up.path = curlx_strdup("/test"); + abort_unless(data->state.up.path, "path allocation failed"); + result = Curl_protocol_mqtt.setup_connection(data, conn); + abort_unless(!result, "MQTT setup failed"); + + result = Curl_protocol_mqtt.do_it(data, &done); + for(i = 0; !result && !done && i < nsends + 4; ++i) { + Curl_pollset_reset(&ps); + result = Curl_protocol_mqtt.doing_pollset(data, &ps); + abort_unless(!result, "MQTT pollset failed"); + abort_unless(ps.n == 1 && ps.sockets[0] == 42, + "unexpected pollset socket"); + fail_unless(ps.actions[0] == (ctx.pending ? CURL_POLL_OUT : CURL_POLL_IN), + "polling the wrong direction for queued output"); + result = Curl_protocol_mqtt.doing(data, &done); + fail_unless(!done || ctx.written == sizeof(ctx.output), + "transfer completed with unsent packet bytes"); + } + fail_unless(result == expected_result, "unexpected transfer result"); + fail_unless(ctx.send_index == nsends, "not all scripted sends were used"); + fail_unless(!memcmp(ctx.output, ctx.expected, ctx.written), + "packets were not sent in order"); + if(!expected_result) { + fail_unless(done, "transfer did not complete"); + fail_unless(ctx.written == sizeof(ctx.output), "missing packet bytes"); + } + else + fail_unless(!done, "send failure marked the transfer complete"); + +unit_test_abort: + Curl_pollset_cleanup(&ps); + if(conn) + Curl_conn_free(data, conn); + if(data) { + data->conn = NULL; + curl_easy_cleanup(data); + } +} + +#endif /* CURL_DISABLE_MQTT */ + +static CURLcode test_unit1693(const char *arg) +{ + UNITTEST_BEGIN_SIMPLE + +#ifndef CURL_DISABLE_MQTT + static const struct t1693_send full[] = { + { T1693_CONNECT_LEN, CURLE_OK }, { T1693_PUBLISH_LEN, CURLE_OK }, + { T1693_DISCONNECT_LEN, CURLE_OK } + }; + static const struct t1693_send partial[] = { + { 3, CURLE_OK }, { 0, CURLE_AGAIN }, { 4, CURLE_OK }, { 19, CURLE_OK }, + { 3, CURLE_OK }, { 4, CURLE_OK }, { 0, CURLE_AGAIN }, { 8, CURLE_OK }, + { 0, CURLE_AGAIN }, { 1, CURLE_OK }, { 0, CURLE_AGAIN }, { 1, CURLE_OK } + }; + static const struct t1693_send zero[] = { + { 0, CURLE_OK }, { T1693_CONNECT_LEN, CURLE_OK }, + { 0, CURLE_OK }, { T1693_PUBLISH_LEN, CURLE_OK }, + { 0, CURLE_OK }, { T1693_DISCONNECT_LEN, CURLE_OK } + }; + static const struct t1693_send send_error[] = { + { T1693_CONNECT_LEN, CURLE_OK }, { 4, CURLE_OK }, { 0, CURLE_SEND_ERROR } + }; + + abort_unless(!curl_global_init(CURL_GLOBAL_ALL), "global init failed"); + t1693_run(full, CURL_ARRAYSIZE(full), CURLE_OK); + t1693_run(partial, CURL_ARRAYSIZE(partial), CURLE_OK); + t1693_run(zero, CURL_ARRAYSIZE(zero), CURLE_OK); + t1693_run(send_error, CURL_ARRAYSIZE(send_error), CURLE_SEND_ERROR); + curl_global_cleanup(); +#endif + + UNITTEST_END_SIMPLE +} From 1ac826a1e064b94000820adc9ac187421ce4f0db Mon Sep 17 00:00:00 2001 From: Daniel Stenberg Date: Mon, 21 Sep 2026 09:15:35 +0200 Subject: [PATCH 182/481] tool_doswin: recognize superscript numbers as reserved Windows recognizes the 8-bit *superscript* digits 1, 2, and 3 as digits and treats them as valid parts of COM# and LPT# device names. Both when provided as ISO8859-1 as well as UTF-8. Reported-by: Joshua Rogers Closes #23011 --- src/tool_doswin.c | 42 ++++++++++++++++++++++++++++++------ tests/tunit/tool1604.c | 48 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 83 insertions(+), 7 deletions(-) diff --git a/src/tool_doswin.c b/src/tool_doswin.c index be6cb510d075..8e2bff535664 100644 --- a/src/tool_doswin.c +++ b/src/tool_doswin.c @@ -272,6 +272,40 @@ static SANITIZEcode msdosify(char ** const sanitized, const char *file_name, } #endif /* MSDOS */ +/* return the number of bytes of the reserved prefix, zero if none exists */ +static int prefix_reserved(const char *p) +{ + if(curl_strnequal(p, "COM", 3) || curl_strnequal(p, "LPT", 3)) { + const uint8_t *up = (const uint8_t *)p; + uint8_t super; + int len; + if(('1' <= p[3]) && (p[3] <= '9')) + return 4; + + /* to complicate things, Windows considers superscript 1, 2, 3 to be valid + numbers and they can be provided in ISO8859-1 or UTF-8. Avoid either + version. */ + len = (up[3] == 0xc2) ? /* UTF-8 */ 4 : 3; + super = up[len]; + + switch(super) { + case 0xb9: /* superscript '1' */ + case 0xb2: /* superscript '2' */ + case 0xb3: /* superscript '3' */ + return len + 1; + } + return 0; + } + else if(curl_strnequal(p, "CON", 3) || + curl_strnequal(p, "PRN", 3) || + curl_strnequal(p, "AUX", 3) || + curl_strnequal(p, "NUL", 3)) + return 3; + else if(curl_strnequal(p, "CLOCK$", 6)) + return 6; + return 0; +} + /* * Rename file_name if it is a reserved dos device name. * @@ -334,13 +368,7 @@ static SANITIZEcode rename_if_reserved_dos(char ** const sanitized, */ for(p = buffer; p; p = (p == buffer && buffer != base ? base : NULL)) { size_t p_len; - int x = (curl_strnequal(p, "CON", 3) || - curl_strnequal(p, "PRN", 3) || - curl_strnequal(p, "AUX", 3) || - curl_strnequal(p, "NUL", 3)) ? 3 : - curl_strnequal(p, "CLOCK$", 6) ? 6 : - (curl_strnequal(p, "COM", 3) || curl_strnequal(p, "LPT", 3)) ? - (('1' <= p[3] && p[3] <= '9') ? 4 : 3) : 0; + int x = prefix_reserved(p); if(!x) continue; diff --git a/tests/tunit/tool1604.c b/tests/tunit/tool1604.c index f16b0bfe0a27..80050b35236e 100644 --- a/tests/tunit/tool1604.c +++ b/tests/tunit/tool1604.c @@ -126,6 +126,15 @@ static CURLcode test_tool1604(const char *arg) { "com1", 0, "_com1", SANITIZE_ERR_OK }, + { "com4", 0, + "_com4", SANITIZE_ERR_OK + }, + { "com6", 0, + "_com6", SANITIZE_ERR_OK + }, + { "com9", 0, + "_com9", SANITIZE_ERR_OK + }, { "com1", SANITIZE_ALLOW_RESERVED, "com1", SANITIZE_ERR_OK }, @@ -170,6 +179,45 @@ static CURLcode test_tool1604(const char *arg) { "COM56", 0, "COM56", SANITIZE_ERR_OK }, + { "COM\xc2\xb9", 0, + "_COM\xc2\xb9", SANITIZE_ERR_OK + }, + { "COM\xc2\xb2", 0, + "_COM\xc2\xb2", SANITIZE_ERR_OK + }, + { "COM\xc2\xb3", 0, + "_COM\xc2\xb3", SANITIZE_ERR_OK + }, + { "COM\xb9", 0, + "_COM\xb9", SANITIZE_ERR_OK + }, + { "COM\xb2", 0, + "_COM\xb2", SANITIZE_ERR_OK + }, + { "COM\xb3", 0, + "_COM\xb3", SANITIZE_ERR_OK + }, + { "lpt\xc2\xb9", 0, + "_lpt\xc2\xb9", SANITIZE_ERR_OK + }, + { "lpt\xc2\xb2", 0, + "_lpt\xc2\xb2", SANITIZE_ERR_OK + }, + { "lpt\xc2\xb3", 0, + "_lpt\xc2\xb3", SANITIZE_ERR_OK + }, + { "lpt\xc2\xb9.txt", 0, + "lpt\xc2\xb9_txt", SANITIZE_ERR_OK + }, + { "lpt\xc2\xb2.txt", 0, + "lpt\xc2\xb2_txt", SANITIZE_ERR_OK + }, + { "lpt\xc2\xb3.txt", 0, + "lpt\xc2\xb3_txt", SANITIZE_ERR_OK + }, + { "COM\xc2\xb3", SANITIZE_ALLOW_RESERVED, + "COM\xc2\xb3", SANITIZE_ERR_OK + }, { NULL, 0, NULL, SANITIZE_ERR_BAD_ARGUMENT }, From 66c41484d0645831ac5ca7c859f56015c3c3a74d Mon Sep 17 00:00:00 2001 From: Daniel Stenberg Date: Mon, 21 Sep 2026 10:44:37 +0200 Subject: [PATCH 183/481] CURLINFO_EFFECTIVE_URL.md: mention a short lifetime Closes #23015 --- docs/libcurl/opts/CURLINFO_EFFECTIVE_URL.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/docs/libcurl/opts/CURLINFO_EFFECTIVE_URL.md b/docs/libcurl/opts/CURLINFO_EFFECTIVE_URL.md index f7adcd810b63..d04a12976a07 100644 --- a/docs/libcurl/opts/CURLINFO_EFFECTIVE_URL.md +++ b/docs/libcurl/opts/CURLINFO_EFFECTIVE_URL.md @@ -29,13 +29,16 @@ CURLcode curl_easy_getinfo(CURL *handle, CURLINFO_EFFECTIVE_URL, char **urlp); Pass in a pointer to a char pointer and get the last used effective URL. -In cases when you have asked libcurl to follow redirects, it may not be the same -value you set with CURLOPT_URL(3). +In cases when you have asked libcurl to follow redirects, it may not be the +same value you set with CURLOPT_URL(3). The **urlp** pointer is NULL or points to private memory. You **must not** free it. It memory gets freed automatically when you call curl_easy_cleanup(3) on the corresponding curl handle. +The returned string has limited life-time. It might get destroyed at the next +libcurl function call involving this same *handle*. + # %PROTOCOLS% # EXAMPLE From e0c8b4b451496d51eaaaa86b3343d4649f7362d2 Mon Sep 17 00:00:00 2001 From: Max Dymond Date: Mon, 21 Sep 2026 10:34:00 +0100 Subject: [PATCH 184/481] base64: avoid repeated input loads when encoding Pack each input triplet into a uint32_t before writing the four encoded characters. This avoids redundant input loads when the compiler cannot rule out aliasing between input and output. Keep byte-wise reads, both alphabets, padding and tail handling unchanged. Document the load-before-store ordering to preserve the optimization. Extend unit1302 with binary round-trip tests for lengths 1 through 256, including output-length and NUL-termination checks. Closes #23017 --- lib/curlx/base64.c | 13 +++++++++---- tests/unit/unit1302.c | 24 ++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 4 deletions(-) diff --git a/lib/curlx/base64.c b/lib/curlx/base64.c index 8cb955f1cfbd..c0d842fef2da 100644 --- a/lib/curlx/base64.c +++ b/lib/curlx/base64.c @@ -207,10 +207,15 @@ static CURLcode base64_encode(const char *table64, return CURLE_OUT_OF_MEMORY; while(insize >= 3) { - *output++ = table64[in[0] >> 2]; - *output++ = table64[((in[0] & 0x03) << 4) | (in[1] >> 4)]; - *output++ = table64[((in[1] & 0x0F) << 2) | ((in[2] & 0xC0) >> 6)]; - *output++ = table64[in[2] & 0x3F]; + /* Load all three input bytes before storing output, avoiding reloads when + the compiler cannot rule out aliasing. Extract four 6-bit indices from + the packed value; byte-wise loads avoid alignment and endian issues. */ + uint32_t bits = ((uint32_t)in[0] << 16) | + ((uint32_t)in[1] << 8) | in[2]; + *output++ = table64[bits >> 18]; + *output++ = table64[(bits >> 12) & 0x3F]; + *output++ = table64[(bits >> 6) & 0x3F]; + *output++ = table64[bits & 0x3F]; insize -= 3; in += 3; } diff --git a/tests/unit/unit1302.c b/tests/unit/unit1302.c index e840486511fc..8a0902ea6a4f 100644 --- a/tests/unit/unit1302.c +++ b/tests/unit/unit1302.c @@ -162,6 +162,30 @@ static CURLcode test_unit1302(const char *arg) curlx_safefree(decoded); } + { + unsigned char binary[256]; + size_t length; + for(i = 0; i < sizeof(binary); i++) + binary[i] = (unsigned char)i; + for(length = 1; length <= sizeof(binary); length++) { + char *encoded; + unsigned char *decoded; + size_t enclen; + size_t declen; + result = curlx_base64_encode(binary, length, &encoded, &enclen); + abort_unless(result == CURLE_OK, "binary encoding failed"); + fail_unless(enclen == (length + 2) / 3 * 4, "wrong encoded size"); + fail_unless(!encoded[enclen], "encoded data is not terminated"); + result = curlx_base64_decode(encoded, &decoded, &declen); + curlx_free(encoded); + abort_unless(result == CURLE_OK, "binary decoding failed"); + fail_unless(declen == length, "wrong decoded size"); + fail_unless(!memcmp(decoded, binary, length), "wrong decoded data"); + fail_unless(!decoded[declen], "decoded data is not terminated"); + curlx_free(decoded); + } + } + for(i = 0; i < CURL_ARRAYSIZE(url); i++) { const struct etest *e = &url[i]; char *out; From aad438d53569de2f29a54f6c65755fb0e7f0ed8f Mon Sep 17 00:00:00 2001 From: Max Dymond Date: Mon, 21 Sep 2026 11:15:14 +0100 Subject: [PATCH 185/481] mprintf: avoid scanning strings twice for unbounded %s When precision is unspecified, out_string() already knows the exact string length. Pass it directly to stream_run() instead of scanning again in stream_zrun(). Keep the bounded, NUL-aware scan for explicit precision, including non-NUL-terminated buffers. Document why the two paths differ. Closes #23019 --- lib/mprintf.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/lib/mprintf.c b/lib/mprintf.c index 7abf487d6633..abe0df4b75ce 100644 --- a/lib/mprintf.c +++ b/lib/mprintf.c @@ -1006,7 +1006,15 @@ static bool out_string(void *userp, width = 0; } - if(stream_zrun(userp, stream, streamn, str, len, donep)) + /* With no precision, len is already the exact length; emit it without + scanning again. With precision, scan up to len but stop at NUL, since + the string may be shorter or the buffer may not be NUL-terminated. */ + if(prec == -1) { + if(stream_run(userp, stream, streamn, + (const unsigned char *)str, len, donep)) + return TRUE; + } + else if(stream_zrun(userp, stream, streamn, str, len, donep)) return TRUE; if(flags & FLAGS_LEFT) { if(stream_pad(userp, stream, streamn, ' ', width, donep)) From 7a6bd027d0de2b6857472d35da090ef60cc29f19 Mon Sep 17 00:00:00 2001 From: Daniel Stenberg Date: Mon, 21 Sep 2026 11:13:13 +0200 Subject: [PATCH 186/481] getinfo: make sure CURLINFO_EFFECTIVE_URL does not contain creds Reported-by: Joshua Rogers Verified in test 1876 Closes #23016 --- lib/getinfo.c | 47 +++++++++++++++++++++++++---- lib/transfer.c | 1 + lib/url.c | 1 + lib/urldata.h | 1 + tests/data/Makefile.am | 1 + tests/data/test1876 | 67 ++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 113 insertions(+), 5 deletions(-) create mode 100644 tests/data/test1876 diff --git a/lib/getinfo.c b/lib/getinfo.c index 23f1de270c49..eda62da5155d 100644 --- a/lib/getinfo.c +++ b/lib/getinfo.c @@ -63,6 +63,7 @@ void Curl_initinfo(struct Curl_easy *data) curlx_safefree(info->contenttype); curlx_safefree(info->wouldredirect); + curlx_safefree(info->effective_url); memset(&info->primary, 0, sizeof(info->primary)); info->retry_after = 0; @@ -76,15 +77,51 @@ void Curl_initinfo(struct Curl_easy *data) #endif } +static CURLcode effective_url(struct Curl_easy *data, + const char **param_charp) +{ + const char *s = Curl_bufref_ptr(&data->state.url); + *param_charp = s ? s : ""; + if(s) { + char *url = NULL; + /* Make a copy of the URL without credentials */ + CURLU *u = curl_url(); + CURLUcode uc; + if(!u) + return CURLE_OUT_OF_MEMORY; + + uc = curl_url_set(u, CURLUPART_URL, s, + CURLU_NON_SUPPORT_SCHEME | + (data->set.path_as_is ? CURLU_PATH_AS_IS : 0)); + if(!uc) + uc = curl_url_set(u, CURLUPART_USER, NULL, 0); + if(!uc) + uc = curl_url_set(u, CURLUPART_PASSWORD, NULL, 0); + if(!uc) + uc = curl_url_get(u, CURLUPART_URL, &url, CURLU_GET_EMPTY); + + curl_url_cleanup(u); + + if(uc || !url) + return CURLE_OUT_OF_MEMORY; + if(strcmp(url, s)) { + /* they are different */ + curlx_free(data->info.effective_url); + data->info.effective_url = url; + *param_charp = url; + } + else + curl_free(url); + } + return CURLE_OK; +} + static CURLcode getinfo_char(struct Curl_easy *data, CURLINFO info, const char **param_charp) { switch(info) { - case CURLINFO_EFFECTIVE_URL: { - const char *s = Curl_bufref_ptr(&data->state.url); - *param_charp = s ? s : ""; - } - break; + case CURLINFO_EFFECTIVE_URL: + return effective_url(data, param_charp); case CURLINFO_EFFECTIVE_METHOD: { const char *m = CURL_EASY_STR(data, STRING_CUSTOMREQUEST); if(!m) { diff --git a/lib/transfer.c b/lib/transfer.c index 5fe962fa0acd..3fc118117e3d 100644 --- a/lib/transfer.c +++ b/lib/transfer.c @@ -503,6 +503,7 @@ CURLcode Curl_pretransfer(struct Curl_easy *data) data->state.authhost.want = data->set.httpauth; data->state.authproxy.want = data->set.proxyauth; curlx_safefree(data->info.wouldredirect); + curlx_safefree(data->info.effective_url); Curl_data_priority_clear_state(data); if(data->set.http_auto_referer) Curl_bufref_free(&data->state.referer); diff --git a/lib/url.c b/lib/url.c index 9026f96d6651..d0c59b0b084b 100644 --- a/lib/url.c +++ b/lib/url.c @@ -270,6 +270,7 @@ CURLcode Curl_close(struct Curl_easy **datap) curlx_safefree(data->state.most_recent_ftp_entrypath); curlx_safefree(data->info.contenttype); curlx_safefree(data->info.wouldredirect); + curlx_safefree(data->info.effective_url); /* No longer a dirty share, if it exists */ if(Curl_share_easy_unlink(data)) diff --git a/lib/urldata.h b/lib/urldata.h index 40a31d2ea7db..d2c025408548 100644 --- a/lib/urldata.h +++ b/lib/urldata.h @@ -409,6 +409,7 @@ struct PureInfo { curl_off_t numconnects; /* how many new connections libcurl created */ char *contenttype; /* the content type of the object */ char *wouldredirect; /* URL this would have been redirected to if asked to */ + char *effective_url; /* when needed */ curl_off_t retry_after; /* info from Retry-After: header */ const char *conn_scheme; int httpcode; /* Recent HTTP, FTP, RTSP or SMTP response code */ diff --git a/tests/data/Makefile.am b/tests/data/Makefile.am index 9714226438e6..a9417ff3a162 100644 --- a/tests/data/Makefile.am +++ b/tests/data/Makefile.am @@ -1750,6 +1750,7 @@ TESTCASES = \ test1849 \ test1851 \ test1862 \ + test1876 \ test1900 \ test1901 \ test1902 \ diff --git a/tests/data/test1876 b/tests/data/test1876 new file mode 100644 index 000000000000..4fc3b9fd0252 --- /dev/null +++ b/tests/data/test1876 @@ -0,0 +1,67 @@ + + + + +netrc + + + +# Server-side + + +HTTP/1.1 301 redir +Content-Length: 0 +Content-Type: text/html +Location: %TESTNUMBER0002 + + + + +HTTP/1.1 200 OK +Content-Length: 0 +Content-Type: text/html + + + + + +# Client-side + + +http + + +verify no credentials present in effective URL after redirect + + +machine %HOSTIP +login netrcuser +password netrcSECRET123 + + + +http://%HOSTIP:%HTTPPORT/%TESTNUMBER --netrc-optional --netrc-file %LOGDIR/netrc -L -w 'EFFECTIVE_URL=%{url_effective}\n' + + + +# Verify data after the test has been "shot" + + +GET /%TESTNUMBER HTTP/1.1 +Host: %HOSTIP:%HTTPPORT +Authorization: Basic %b64[netrcuser:netrcSECRET123]b64% +User-Agent: curl/%VERSION +Accept: */* + +GET /%TESTNUMBER0002 HTTP/1.1 +Host: %HOSTIP:%HTTPPORT +Authorization: Basic %b64[netrcuser:netrcSECRET123]b64% +User-Agent: curl/%VERSION +Accept: */* + + + +EFFECTIVE_URL=http://%HOSTIP:%HTTPPORT/%TESTNUMBER0002 + + + From d1317b5d4a5c8430133a588fb812d5c5fa327b79 Mon Sep 17 00:00:00 2001 From: Daniel Stenberg Date: Mon, 21 Sep 2026 09:50:17 +0200 Subject: [PATCH 187/481] parsedate: remove the unused leading three letters The three first letters of the weekdays are never actually used in the weekday array so drop them. Also make the array local. Closes #23013 --- lib/parsedate.c | 16 +++++++++------- tests/libtest/lib517.c | 4 ++++ 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/lib/parsedate.c b/lib/parsedate.c index 9630a988b962..ef7ddd057141 100644 --- a/lib/parsedate.c +++ b/lib/parsedate.c @@ -103,10 +103,6 @@ const char * const Curl_month[] = { #define PARSEDATE_SOONER 2 #endif -static const char * const weekday[] = { - "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday" -}; - struct tzinfo { uint32_t tz; int16_t offset; /* +/- in minutes */ @@ -248,10 +244,16 @@ UNITTEST int checkday(const char *check, size_t len) break; } if((len > 3) && (day != -1)) { - /* when more than three letters are provided, verify the full name case + /* when more than three letters are provided, verify the tail name case insensitively */ - size_t wlen = strlen(weekday[day]); - if((len != wlen) || !curl_strnequal(&check[3], &weekday[day][3], len - 3)) + static const char * const daysuffix[] = { + /* without the leading three letters */ + "day", "sday", "nesday", "rsday", "day", "urday", "day" + }; + + size_t wlen = strlen(daysuffix[day]); + if(((len - 3) != wlen) || + !curl_strnequal(&check[3], daysuffix[day], wlen)) return -1; } return day; diff --git a/tests/libtest/lib517.c b/tests/libtest/lib517.c index 4df2b0c3f937..38abfc3f8a81 100644 --- a/tests/libtest/lib517.c +++ b/tests/libtest/lib517.c @@ -32,6 +32,10 @@ static CURLcode test_lib517(const char *URL) static const struct dcheck dates[] = { { "Sat May 5 GMT 11:56:27 2035", 2061978987 }, + { "Sat, May 5 GMT 11:56:27 2035", 2061978987 }, + { "Saturday, May 5 GMT 11:56:27 2035", 2061978987 }, + { "Saturdax, May 5 GMT 11:56:27 2035", -1 }, + { "Saturda, May 5 GMT 11:56:27 2035", -1 }, { "Sun, 06 Nov 1994 08:49:37 GMT", 784111777 }, { "Sunday, 06-Nov-94 08:49:37 GMT", 784111777 }, { "Sun Nov 6 08:49:37 1994", 784111777 }, From 557a52f21e37a626725a9e708c02af129c3ece1b Mon Sep 17 00:00:00 2001 From: Stefan Eissing Date: Mon, 21 Sep 2026 09:19:43 +0200 Subject: [PATCH 188/481] http: only enable Negotiate/NTLM to allowed origins Restrict Negotiate/NTLM to only "allowed" origins, irregardless of where credentials are sourced from. Closes #23012 --- lib/http.c | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/lib/http.c b/lib/http.c index 3b3c542c90fd..8894af10082b 100644 --- a/lib/http.c +++ b/lib/http.c @@ -691,7 +691,8 @@ static CURLcode output_auth_headers(struct Curl_easy *data, (proxy && !Curl_checkProxyheaders(data, conn, STRCONST("Proxy-authorization"))) || #endif - (!proxy && !Curl_checkheaders(data, STRCONST("Authorization")))) { + (!proxy && !Curl_checkheaders(data, STRCONST("Authorization")) && + Curl_auth_allowed_to_host(data))) { auth = "Negotiate"; result = Curl_output_negotiate(data, conn, proxy); if(result) @@ -704,10 +705,14 @@ static CURLcode output_auth_headers(struct Curl_easy *data, #endif #ifdef USE_NTLM if(authstatus->picked == CURLAUTH_NTLM) { - auth = "NTLM"; - result = Curl_output_ntlm(data, proxy); - if(result) - return result; + if(proxy || Curl_auth_allowed_to_host(data)) { + auth = "NTLM"; + result = Curl_output_ntlm(data, proxy); + if(result) + return result; + } + else + authstatus->done = TRUE; } else #endif From ce359773039d9925935ae21f345c312742b1fea9 Mon Sep 17 00:00:00 2001 From: Filippo Tedeschi Date: Mon, 21 Sep 2026 15:13:19 +0200 Subject: [PATCH 189/481] getinfo: make sure CURLINFO_REDIRECT_URL does not contain creds Follow-up to 7a6bd02: make sure data->info.wouldredirect (exposed via CURLINFO_REDIRECT_URL and %{redirect_url}) does not contain credentials inherited from the original request URL or netrc when resolving relative redirect targets in FOLLOW_FAKE mode. Verified in test 1877 Closes #23020 --- lib/http.c | 20 ++++++++++++++++ tests/data/Makefile.am | 1 + tests/data/test1877 | 54 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 75 insertions(+) create mode 100644 tests/data/test1877 diff --git a/lib/http.c b/lib/http.c index 8894af10082b..2d8f44e1a627 100644 --- a/lib/http.c +++ b/lib/http.c @@ -1294,6 +1294,26 @@ CURLcode Curl_http_follow(struct Curl_easy *data, const char *newurl, if(type == FOLLOW_FAKE) { /* we are only figuring out the new URL if we would have followed locations but now we are done so we can get out! */ + if(!uc) { + CURLU *u = curl_url(); + char *nocred = NULL; + + if(!u) { + curlx_free(follow_url); + return CURLE_OUT_OF_MEMORY; + } + if(!curl_url_set(u, CURLUPART_URL, follow_url, + CURLU_NON_SUPPORT_SCHEME | + (data->set.path_as_is ? CURLU_PATH_AS_IS : 0)) && + !curl_url_set(u, CURLUPART_USER, NULL, 0) && + !curl_url_set(u, CURLUPART_PASSWORD, NULL, 0)) + (void)curl_url_get(u, CURLUPART_URL, &nocred, CURLU_GET_EMPTY); + curl_url_cleanup(u); + if(nocred) { + curlx_free(follow_url); + follow_url = nocred; + } + } data->info.wouldredirect = follow_url; if(reachedmax) { diff --git a/tests/data/Makefile.am b/tests/data/Makefile.am index a9417ff3a162..10b6d78b28af 100644 --- a/tests/data/Makefile.am +++ b/tests/data/Makefile.am @@ -1751,6 +1751,7 @@ TESTCASES = \ test1851 \ test1862 \ test1876 \ + test1877 \ test1900 \ test1901 \ test1902 \ diff --git a/tests/data/test1877 b/tests/data/test1877 new file mode 100644 index 000000000000..508fe53c8a5a --- /dev/null +++ b/tests/data/test1877 @@ -0,0 +1,54 @@ + + + + +netrc +CURLINFO_REDIRECT_URL + + + +# Server-side + + +HTTP/1.1 301 redir +Content-Length: 0 +Content-Type: text/html +Location: %TESTNUMBER0002 + + + + +# Client-side + + +http + + +verify no credentials present in redirect URL after redirect + + +machine %HOSTIP +login netrcuser +password netrcSECRET123 + + + +http://%HOSTIP:%HTTPPORT/%TESTNUMBER --netrc-optional --netrc-file %LOGDIR/netrc -w 'REDIRECT_URL=%{redirect_url}\n' + + + +# Verify data after the test has been "shot" + + +GET /%TESTNUMBER HTTP/1.1 +Host: %HOSTIP:%HTTPPORT +Authorization: Basic %b64[netrcuser:netrcSECRET123]b64% +User-Agent: curl/%VERSION +Accept: */* + + + +REDIRECT_URL=http://%HOSTIP:%HTTPPORT/%TESTNUMBER0002 + + + From 3aa67d5e49b99c922a40faac941e48f644690c91 Mon Sep 17 00:00:00 2001 From: Daniel Stenberg Date: Mon, 21 Sep 2026 16:39:22 +0200 Subject: [PATCH 190/481] digest: quote the digest-uri param as well Verified by test2967 Fixes #23018 Reported-by: Yechan Bae Closes #23022 --- lib/vauth/digest.c | 11 ++++--- tests/data/Makefile.am | 1 + tests/data/test2967 | 65 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 73 insertions(+), 4 deletions(-) create mode 100644 tests/data/test2967 diff --git a/lib/vauth/digest.c b/lib/vauth/digest.c index 1903ffd07aef..3642dc714eb5 100644 --- a/lib/vauth/digest.c +++ b/lib/vauth/digest.c @@ -371,6 +371,7 @@ CURLcode Curl_auth_create_digest_md5_message(struct Curl_easy *data, char *qrealm; char *qnonce; char *quserp; + char *qspn; /* Decode the challenge message */ CURLcode result = auth_decode_digest_md5_message(chlg, @@ -484,23 +485,25 @@ CURLcode Curl_auth_create_digest_md5_message(struct Curl_easy *data, for(i = 0; i < MD5_DIGEST_LEN; i++) curl_msnprintf(&resp_hash_hex[2 * i], 3, "%02x", digest[i]); - /* escape double quotes and backslashes in the username, realm and nonce as - necessary */ + /* escape double quotes and backslashes in the username, realm, nonce and + spn as necessary */ qrealm = auth_digest_string_quoted(realm); qnonce = auth_digest_string_quoted(nonce); quserp = auth_digest_string_quoted(userp); - if(qrealm && qnonce && quserp) + qspn = auth_digest_string_quoted(spn); + if(qrealm && qnonce && quserp && qspn) /* Generate the response */ response = curl_maprintf("username=\"%s\",realm=\"%s\",nonce=\"%s\"," "cnonce=\"%s\",nc=\"%s\",digest-uri=\"%s\"," "response=%s,qop=%s", quserp, qrealm, qnonce, - cnonce, nonceCount, spn, resp_hash_hex, qop); + cnonce, nonceCount, qspn, resp_hash_hex, qop); curlx_free(qrealm); curlx_free(qnonce); curlx_free(quserp); curlx_free(spn); + curlx_free(qspn); if(!response) return CURLE_OUT_OF_MEMORY; diff --git a/tests/data/Makefile.am b/tests/data/Makefile.am index 10b6d78b28af..8b18e2ca659a 100644 --- a/tests/data/Makefile.am +++ b/tests/data/Makefile.am @@ -2029,6 +2029,7 @@ TESTCASES = \ test2885 \ test2918 \ test2924 \ + test2967 \ test3000 \ test3001 \ test3002 \ diff --git a/tests/data/test2967 b/tests/data/test2967 new file mode 100644 index 000000000000..6b72807fe2d7 --- /dev/null +++ b/tests/data/test2967 @@ -0,0 +1,65 @@ + + + + +SMTP +SASL +SASL AUTH DIGEST-MD5 +RFC2831 +RFC4954 + + + +# Server-side + + +AUTH DIGEST-MD5 +REPLY AUTH 334 %b64[realm="cu\"rl",nonce="5300d17a1d695bd411e4cdf96f9548c23ced6175",algorithm=md5-sess,qop="auth"]b64% +REPLY %b64[username="us\"er",realm="cu\"rl",nonce="5300d17a1d695bd411e4cdf96f9548c23ced6175",cnonce="34333231353332313633323137333231",nc="00000001",digest-uri="smtp\",injected=1,x=\"/127.0.0.1",response=c6f01f4f75dcb83068b66c8b4f347400,qop=auth]b64% 334 %b64[rspauth=9ea859cb9d90c37ca30d49d35378630c]b64% +REPLY 235 Authenticated + + + +# Client-side + + +smtp + + +!SSPI +Debug +crypto +digest + + +CURL_ENTROPY=12345678 + + +SMTP DIGEST-MD5 with set service name + + +mail body + + +smtp://%HOSTIP:%SMTPPORT/%TESTNUMBER --mail-rcpt recipient@example.com --mail-from sender@example.com -u "us\"er:secret" -T - --service-name 'smtp",injected=1,x="' + + + +# Verify data after the test has been "shot" + + +EHLO %TESTNUMBER +AUTH DIGEST-MD5 +%b64[username="us\"er",realm="cu\"rl",nonce="5300d17a1d695bd411e4cdf96f9548c23ced6175",cnonce="34333231353332313633323137333231",nc="00000001",digest-uri="smtp\",injected=1,x=\"/127.0.0.1",response=c6f01f4f75dcb83068b66c8b4f347400,qop=auth]b64% + +MAIL FROM:%LTsender@example.com%GT +RCPT TO:%LTrecipient@example.com%GT +DATA +QUIT + + +mail body +. + + + From 7934b5400f3e95ee63c3658c9bda2ec35e0afb31 Mon Sep 17 00:00:00 2001 From: Joshua Rogers Date: Fri, 18 Sep 2026 20:39:58 +0200 Subject: [PATCH 191/481] capsule: reject a capsule that can never be buffered The decoder waited for the full advertised payload before checking that it fits anywhere, so a peer advertising a length the receive queue can never hold made cf_capsule_recv() return CURLE_AGAIN forever. Check the destination buffer first and fail when the receive queue is full and still short of the capsule. Also bound the length so that offset + payload_len cannot wrap. Add a unit3400 case for the full-queue rejection. Closes #22998 --- lib/vquic/capsule.c | 18 ++++++++++++------ tests/unit/unit3400.c | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 6 deletions(-) diff --git a/lib/vquic/capsule.c b/lib/vquic/capsule.c index 4bbdae3ace44..e6456062c1d7 100644 --- a/lib/vquic/capsule.c +++ b/lib/vquic/capsule.c @@ -256,7 +256,7 @@ size_t Curl_capsule_process_udp_raw(struct Curl_cfilter *cf, *err = CURLE_RECV_ERROR; return 0; } - if(capsule_length - 1 >= (uint64_t)SIZE_MAX) { + if(capsule_length - 1 > (uint64_t)(SIZE_MAX - offset)) { infof(data, "Error! Capsule length too large: %" CURL_FORMAT_CURL_OFF_T, (curl_off_t)capsule_length); *err = CURLE_RECV_ERROR; @@ -264,11 +264,6 @@ size_t Curl_capsule_process_udp_raw(struct Curl_cfilter *cf, } payload_len = (size_t)(capsule_length - 1); - if(Curl_bufq_len(recvbufq) < offset + payload_len) { - *err = CURLE_AGAIN; - return 0; - } - if(payload_len > len) { infof(data, "UDP payload does not fit destination buffer: %zu > %zu", payload_len, len); @@ -277,6 +272,17 @@ size_t Curl_capsule_process_udp_raw(struct Curl_cfilter *cf, return 0; } + if(Curl_bufq_len(recvbufq) < offset + payload_len) { + if(Curl_bufq_is_full(recvbufq)) { + infof(data, "Error! Capsule does not fit the receive buffer: %zu", + payload_len); + *err = CURLE_RECV_ERROR; + return 0; + } + *err = CURLE_AGAIN; + return 0; + } + Curl_bufq_skip(recvbufq, offset); if(!payload_len) { *err = CURLE_OK; diff --git a/tests/unit/unit3400.c b/tests/unit/unit3400.c index 364a2b540f11..539f16f84d87 100644 --- a/tests/unit/unit3400.c +++ b/tests/unit/unit3400.c @@ -241,6 +241,41 @@ static void test_capsule_decode_paths(void) Curl_bufq_free(&q); } + +static void test_capsule_full_buffer_reject(void) +{ + /* A capsule whose payload can never fit the bounded bufq must fail + instead of asking for more data forever. */ + struct bufq q; + unsigned char out[256]; + unsigned char cap[128]; + CURLcode err = CURLE_OK; + size_t nread; + /* type 0x00, length 201 as a 2-byte varint, context 0x00 */ + static const unsigned char hdr[] = { 0x00, 0x40, 0xC9, 0x00 }; + + Curl_bufq_init2(&q, 32, 4, BUFQ_OPT_NONE); + + memset(cap, 0x5A, sizeof(cap)); + memcpy(cap, hdr, sizeof(hdr)); + + /* the queue can still grow, so wait for the rest */ + queue_bytes(&q, cap, 64); + nread = Curl_capsule_process_udp_raw(NULL, NULL, &q, out, sizeof(out), &err); + fail_unless(err == CURLE_AGAIN, "partial capsule should return AGAIN"); + fail_unless(nread == 0, "partial capsule should read zero bytes"); + + /* the queue is full and the capsule is still short, it never completes */ + queue_bytes(&q, cap + 64, sizeof(cap) - 64); + fail_unless(Curl_bufq_is_full(&q), "queue should be full"); + nread = Curl_capsule_process_udp_raw(NULL, NULL, &q, out, sizeof(out), &err); + fail_unless(err == CURLE_RECV_ERROR, + "unbufferable capsule should return RECV_ERROR"); + fail_unless(nread == 0, "unbufferable capsule should read zero bytes"); + + Curl_bufq_free(&q); +} + #endif /* USE_NGTCP2 && !CURL_DISABLE_PROXY && !CURL_DISABLE_HTTP */ static CURLcode test_unit3400(const char *arg) @@ -257,6 +292,7 @@ static CURLcode test_unit3400(const char *arg) test_capsule_encode_decode_roundtrip(); test_capsule_decode_paths(); test_capsule_sequential_decode(); + test_capsule_full_buffer_reject(); #endif UNITTEST_END_SIMPLE From 53be819ca8291b591332aeeae480bd0ae0d08d24 Mon Sep 17 00:00:00 2001 From: Max Dymond Date: Mon, 21 Sep 2026 15:08:27 +0100 Subject: [PATCH 192/481] escape: use hex-pair lookup in curl_easy_escape Copy precomputed uppercase hex pairs instead of looking up each digit separately. Preserve literal-run batching and allocation behavior. Add coverage for all 256 byte values, checking exact encoded output and round-trip decoding. Closes #23021 --- lib/escape.c | 70 +++++++++++++++++++++++++++++++++++++++-- tests/libtest/lib1396.c | 38 ++++++++++++++++++++++ 2 files changed, 106 insertions(+), 2 deletions(-) diff --git a/lib/escape.c b/lib/escape.c index 0a1f0a953c42..64f6e4ab7376 100644 --- a/lib/escape.c +++ b/lib/escape.c @@ -83,6 +83,73 @@ static const unsigned char hextable[256] = { NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE }; +static const char hexpairs[][2] = { + {'0', '0'}, {'0', '1'}, {'0', '2'}, {'0', '3'}, + {'0', '4'}, {'0', '5'}, {'0', '6'}, {'0', '7'}, + {'0', '8'}, {'0', '9'}, {'0', 'A'}, {'0', 'B'}, + {'0', 'C'}, {'0', 'D'}, {'0', 'E'}, {'0', 'F'}, + {'1', '0'}, {'1', '1'}, {'1', '2'}, {'1', '3'}, + {'1', '4'}, {'1', '5'}, {'1', '6'}, {'1', '7'}, + {'1', '8'}, {'1', '9'}, {'1', 'A'}, {'1', 'B'}, + {'1', 'C'}, {'1', 'D'}, {'1', 'E'}, {'1', 'F'}, + {'2', '0'}, {'2', '1'}, {'2', '2'}, {'2', '3'}, + {'2', '4'}, {'2', '5'}, {'2', '6'}, {'2', '7'}, + {'2', '8'}, {'2', '9'}, {'2', 'A'}, {'2', 'B'}, + {'2', 'C'}, {'2', 'D'}, {'2', 'E'}, {'2', 'F'}, + {'3', '0'}, {'3', '1'}, {'3', '2'}, {'3', '3'}, + {'3', '4'}, {'3', '5'}, {'3', '6'}, {'3', '7'}, + {'3', '8'}, {'3', '9'}, {'3', 'A'}, {'3', 'B'}, + {'3', 'C'}, {'3', 'D'}, {'3', 'E'}, {'3', 'F'}, + {'4', '0'}, {'4', '1'}, {'4', '2'}, {'4', '3'}, + {'4', '4'}, {'4', '5'}, {'4', '6'}, {'4', '7'}, + {'4', '8'}, {'4', '9'}, {'4', 'A'}, {'4', 'B'}, + {'4', 'C'}, {'4', 'D'}, {'4', 'E'}, {'4', 'F'}, + {'5', '0'}, {'5', '1'}, {'5', '2'}, {'5', '3'}, + {'5', '4'}, {'5', '5'}, {'5', '6'}, {'5', '7'}, + {'5', '8'}, {'5', '9'}, {'5', 'A'}, {'5', 'B'}, + {'5', 'C'}, {'5', 'D'}, {'5', 'E'}, {'5', 'F'}, + {'6', '0'}, {'6', '1'}, {'6', '2'}, {'6', '3'}, + {'6', '4'}, {'6', '5'}, {'6', '6'}, {'6', '7'}, + {'6', '8'}, {'6', '9'}, {'6', 'A'}, {'6', 'B'}, + {'6', 'C'}, {'6', 'D'}, {'6', 'E'}, {'6', 'F'}, + {'7', '0'}, {'7', '1'}, {'7', '2'}, {'7', '3'}, + {'7', '4'}, {'7', '5'}, {'7', '6'}, {'7', '7'}, + {'7', '8'}, {'7', '9'}, {'7', 'A'}, {'7', 'B'}, + {'7', 'C'}, {'7', 'D'}, {'7', 'E'}, {'7', 'F'}, + {'8', '0'}, {'8', '1'}, {'8', '2'}, {'8', '3'}, + {'8', '4'}, {'8', '5'}, {'8', '6'}, {'8', '7'}, + {'8', '8'}, {'8', '9'}, {'8', 'A'}, {'8', 'B'}, + {'8', 'C'}, {'8', 'D'}, {'8', 'E'}, {'8', 'F'}, + {'9', '0'}, {'9', '1'}, {'9', '2'}, {'9', '3'}, + {'9', '4'}, {'9', '5'}, {'9', '6'}, {'9', '7'}, + {'9', '8'}, {'9', '9'}, {'9', 'A'}, {'9', 'B'}, + {'9', 'C'}, {'9', 'D'}, {'9', 'E'}, {'9', 'F'}, + {'A', '0'}, {'A', '1'}, {'A', '2'}, {'A', '3'}, + {'A', '4'}, {'A', '5'}, {'A', '6'}, {'A', '7'}, + {'A', '8'}, {'A', '9'}, {'A', 'A'}, {'A', 'B'}, + {'A', 'C'}, {'A', 'D'}, {'A', 'E'}, {'A', 'F'}, + {'B', '0'}, {'B', '1'}, {'B', '2'}, {'B', '3'}, + {'B', '4'}, {'B', '5'}, {'B', '6'}, {'B', '7'}, + {'B', '8'}, {'B', '9'}, {'B', 'A'}, {'B', 'B'}, + {'B', 'C'}, {'B', 'D'}, {'B', 'E'}, {'B', 'F'}, + {'C', '0'}, {'C', '1'}, {'C', '2'}, {'C', '3'}, + {'C', '4'}, {'C', '5'}, {'C', '6'}, {'C', '7'}, + {'C', '8'}, {'C', '9'}, {'C', 'A'}, {'C', 'B'}, + {'C', 'C'}, {'C', 'D'}, {'C', 'E'}, {'C', 'F'}, + {'D', '0'}, {'D', '1'}, {'D', '2'}, {'D', '3'}, + {'D', '4'}, {'D', '5'}, {'D', '6'}, {'D', '7'}, + {'D', '8'}, {'D', '9'}, {'D', 'A'}, {'D', 'B'}, + {'D', 'C'}, {'D', 'D'}, {'D', 'E'}, {'D', 'F'}, + {'E', '0'}, {'E', '1'}, {'E', '2'}, {'E', '3'}, + {'E', '4'}, {'E', '5'}, {'E', '6'}, {'E', '7'}, + {'E', '8'}, {'E', '9'}, {'E', 'A'}, {'E', 'B'}, + {'E', 'C'}, {'E', 'D'}, {'E', 'E'}, {'E', 'F'}, + {'F', '0'}, {'F', '1'}, {'F', '2'}, {'F', '3'}, + {'F', '4'}, {'F', '5'}, {'F', '6'}, {'F', '7'}, + {'F', '8'}, {'F', '9'}, {'F', 'A'}, {'F', 'B'}, + {'F', 'C'}, {'F', 'D'}, {'F', 'E'}, {'F', 'F'} +}; + /* Escapes for URL the given unescaped string of given length. * 'data' is ignored since 7.82.0. */ @@ -124,8 +191,7 @@ char *curl_easy_escape(CURL *curl, const char *string, int length) f = string; /* encode it */ target[0] = '%'; - target[1] = Curl_udigits[in >> 4]; - target[2] = Curl_udigits[in & 0x0F]; + memcpy(target + 1, hexpairs[in], 2); target += 3; } } diff --git a/tests/libtest/lib1396.c b/tests/libtest/lib1396.c index 902a859e27c1..1eae1a88e541 100644 --- a/tests/libtest/lib1396.c +++ b/tests/libtest/lib1396.c @@ -117,5 +117,43 @@ static CURLcode test_lib1396(const char *arg) curl_free(out); } + { + unsigned char binary[256]; + char expected[sizeof(binary) * 3 + 1]; + const char digits[] = "0123456789ABCDEF"; + size_t encoded_length = 0; + unsigned int byte; + char *encoded; + char *decoded; + int decoded_length; + + for(byte = 0; byte < sizeof(binary); byte++) { + binary[byte] = (unsigned char)byte; + if((byte >= 'a' && byte <= 'z') || (byte >= 'A' && byte <= 'Z') || + (byte >= '0' && byte <= '9') || byte == '-' || byte == '.' || + byte == '_' || byte == '~') + expected[encoded_length++] = (char)byte; + else { + expected[encoded_length++] = '%'; + expected[encoded_length++] = digits[byte >> 4]; + expected[encoded_length++] = digits[byte & 15]; + } + } + expected[encoded_length] = 0; + encoded = curl_easy_escape(easy, (const char *)binary, sizeof(binary)); + abort_unless(encoded, "binary escape failed"); + fail_unless(!strcmp(encoded, expected), "wrong binary escape output"); + decoded = curl_easy_unescape(easy, encoded, 0, &decoded_length); + curl_free(encoded); + abort_unless(decoded, "binary unescape failed"); + fail_unless(decoded_length == sizeof(binary), "wrong binary output size"); + if(decoded_length == sizeof(binary)) { + fail_unless(!memcmp(decoded, binary, sizeof(binary)), + "wrong binary data"); + fail_unless(!decoded[decoded_length], "binary output not terminated"); + } + curl_free(decoded); + } + UNITTEST_END(t1396_stop(easy)) } From c362f034692e05ecb4d33edb740ba64c63a20add Mon Sep 17 00:00:00 2001 From: Daniel Stenberg Date: Tue, 22 Sep 2026 07:52:49 +0200 Subject: [PATCH 193/481] RELEASE-NOTES: synced --- RELEASE-NOTES | 76 +++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 64 insertions(+), 12 deletions(-) diff --git a/RELEASE-NOTES b/RELEASE-NOTES index 71d16cbcdb99..f567ba78d3a7 100644 --- a/RELEASE-NOTES +++ b/RELEASE-NOTES @@ -4,8 +4,8 @@ curl and libcurl 8.23.0 Command line options: 278 curl_easy_setopt() options: 312 Public functions in libcurl: 100 - Authors: 1532 - Contributors: 3812 + Authors: 1536 + Contributors: 3819 This release includes the following changes: @@ -16,9 +16,11 @@ This release includes the following changes: This release includes the following bugfixes: + o base64: avoid repeated input loads when encoding [114] o base64: optimize decode lookup table and quantum loop [88] o build: convert HAS_TIME_T_UNSIGNED to a compile-time check [65] o build: drop `HTTP_ONLY` option [23] + o capsule: reject a capsule that can never be buffered [84] o clang-tidy: merge nested `if()`s [39] o config2setopts: always set the security options for SCP/SFTP [67] o conncache: avoid evicting connect-only connections [79] @@ -30,13 +32,16 @@ This release includes the following bugfixes: o curl_addrinfo: drop redundant parentheses [41] o curl_fopen: restore the uid and gid checks [70] o curl_formadd.md: mention the strlen() for names as well [29] + o CURLINFO_EFFECTIVE_URL.md: mention a short lifetime [116] o CURLOPT*ISSUERCERT*: not verified cryptographically [28] o CURLOPT_SHARE.md: remove bad explanation for cookie sharing [7] o CURLOPT_SSL_CTX_FUNCTION.md: client cert caveats [87] o cw-out: avoid O(n^2) work while buffering paused output [74] + o cw-pause: fix O(N^2) list traversal in cw_pause_flush [137] o digest: check peer equality for staleness [24] o digest: fix maximum length quoted value parsing [22] o digest: parse without storing 'value' in local buffer [20] + o digest: quote the digest-uri param as well [85] o dnscache/conncache tweaks [75] o docs: add the missing .html to the url-encode book link [3] o doh: reject undersized HTTPS RDATA [11] @@ -44,10 +49,16 @@ This release includes the following bugfixes: o easy: duplicating a zero byte memory area needs no malloc [91] o easy: make ca_cache_timeout standalone [51] o escape: polish encoding and decoding for speed [37] + o escape: use hex-pair lookup in curl_easy_escape [77] + o examples: clean up crawler link parsing resources [119] o fnmatch: replace recursive matcher with iterative greedy algorithm [72] + o formdata: reject a negative content length in curl_formadd [128] + o ftp: require TLS on the data connection for implicit FTPS [126] o ftp: tighten use_ssl check for conn reuse [19] o ftp: URL encode file names when wildcarding [61] o ftplistparser: no more overwriting set.write_func [100] + o getinfo: make sure CURLINFO_EFFECTIVE_URL does not contain creds [107] + o getinfo: make sure CURLINFO_REDIRECT_URL does not contain creds [94] o getparam: switch off CONNECT headers when using -J [4] o glob: reduce the number of allocations for URLs that don't need globbing. [109] o global_init: tidy up, simplify, fix issues [43] @@ -62,6 +73,9 @@ This release includes the following bugfixes: o http2: don't send a CURLE_SEND_ERROR after a valid http2 response [71] o http2: initialize URL handle for server pushes [104] o http2: update a refused stream's error after GOAWAY [102] + o http: abide to close-rules when transfer-encoding off [132] + o http: ignore Proxy-Authenticate unless the response comes from a proxy [120] + o http: only enable Negotiate/NTLM to allowed origins [99] o http: reject resumed uploads [16] o httpsig-key.md: correct the generating Ed25519 keys commands [97] o imap: compare selected mailbox names case-sensitively [57] @@ -75,7 +89,9 @@ This release includes the following bugfixes: o mbedtls: fix ticket handling [52] o mime: bound Curl_mime_prepare_headers() recursion depth [76] o mime: do curl_mime_free() non-recursively [48] + o mprintf: avoid scanning strings twice for unbounded %s [110] o mprintf: stage output and emit it in runs [38] + o mqtt: drain queued output before advancing the state machine [118] o mulit_ev: remove socket references on forget [89] o multi-event: fix timeout of pending transfers [111] o multi: reduce struct size with small cleanups [25] @@ -92,6 +108,7 @@ This release includes the following bugfixes: o os400: document curl_formadd_CCSID multi-chunk peculiarity [5] o ossl_verifyhost: remove assumption of null termination of ASN1_STRING. [26] o parsedate: find day names, months and time zones faster [68] + o parsedate: remove the unused leading three letters [106] o perf-tests: output two-digital decimals always [96] o pingpong: add a max loop counter for Curl_pp_readresp [113] o rtsp: accept a Content-Length body on methods that expected none [115] @@ -99,7 +116,9 @@ This release includes the following bugfixes: o runtests: Added runnernums in order to have O(1) lookup [45] o schannel: auto_client_cert conn matching [17] o schannel: clear borrowed sslContext on close [15] + o socks: support CURLAUTH_ONLY in CURLOPT_SOCKS5_AUTH [124] o ssl: dedup ssl_fsslctx [35] + o strparse: speed up number parsing [129] o telnet: make it work through proxy tunnels [112] o test 1605: move from unit to libtest [49] o test1399: improve reliability [9] @@ -107,8 +126,13 @@ This release includes the following bugfixes: o tftp: verify direction in a few places [101] o tool: (re)set SSL verification in --libcurl output [1] o tool: buffer headers before remote filename selection [98] + o tool_doswin: recognize superscript numbers as reserved [117] o tool_easysrc: reset the handle between --next operations [90] + o tool_getparam: do not print the value of a redacted argument [127] o tool_help: guard category[2] access when category is bare "-" [59] + o tool_ipfs: keep the path percent-encoded when rewriting to the gateway [138] + o tool_operate: clear the retry flag when the output file fails [122] + o u8_strset, u32_ptrset: fix boundaries handling [131] o uint-bset: skip unused slots when counting o url: match connections without force reuse and candidates [95] o url: ntlm/negotiate, prohibit connection reuse on follows [18] @@ -116,6 +140,7 @@ This release includes the following bugfixes: o urldata: remove conn->given [40] o urldata: remove ssl_primary_config from easy handle [78] o vquic: fix preprocessor check for old Darwin versions [8] + o vssh: do not busy-loop in blocking state machine without a timeout [121] o vtls: fix Curl_cert_hostcheck when the pattern is not a C string [12] o warnless: dedupe Intel compiler warning suppression pragmas [42] o x509: stricter parsing [2] @@ -139,16 +164,18 @@ Planned upcoming removals include: This release would not have looked like this without help, code, reports and advice from friends like these: - Adam Shirt, Artem Prilutskiy, Aviv Engelberg, Axel Mierczuk, Bobbeh Rhino, - Christian Hesse, Christian Ullrich, Claude, Dan Fandrich, Daniel Lang, - Daniel Stenberg, David Benjamin, Fengxiaoxx on github, Florent Castelli, - Fredrik Blau, Huang Yiheng, Ilias Aberkane, Joshua Rogers, ldm0, Luna Tong, - Martin Dukek, Max Dymond, Mihai-Gabriel Marin, Muhamad Arga Reksapati, - Paolo Ganci, Patrick Monnerat, rawsun007, Ray Satiro, renovate[bot], - Ron Kuper, Roshan Ramani, Rudolf Polzer, Samuel Henrique, Skye Soss, - Stanislav Fort, Stefan Eissing, Stephen Psaradellis, szedenik-adam on github, - Thanniru Sai Teja, Tyler Yankee, Un1q32, Viktor Szakats, xhon-pelushi - (43 contributors) + 1rhino2 on hackerone, Adam Shirt, Artem Prilutskiy, Aviv Engelberg, + Axel Mierczuk, Bobbeh Rhino, Christian Hesse, Christian Ullrich, Claude, + Dan Fandrich, Daniel Lang, Daniel Stenberg, David Benjamin, + Fengxiaoxx on github, Filippo Tedeschi, Florent Castelli, Fredrik Blau, + Huang Yiheng, Ilias Aberkane, Jorge Rocamora, Joshua Rogers, ldm0, Luna Tong, + Martin Dukek, Max Dymond, Mihai-Gabriel Marin, minnnjuuu, + Muhamad Arga Reksapati, OSS-Fuzz, Paolo Ganci, Patrick Monnerat, rawsun007, + Ray Satiro, renovate[bot], Rhino, Richard Payne, Ron Kuper, Roshan Ramani, + Rudolf Polzer, Samuel Henrique, Skye Soss, Stanislav Fort, Stefan Eissing, + Stephen Psaradellis, szedenik-adam on github, Thanniru Sai Teja, + Tyler Yankee, Un1q32, Viktor Szakats, xhon-pelushi, Yechan Bae + (51 contributors) References to bug reports and discussions on issues: @@ -228,12 +255,15 @@ References to bug reports and discussions on issues: [74] = https://curl.se/bug/?i=22896 [75] = https://curl.se/bug/?i=22900 [76] = https://curl.se/bug/?i=22971 + [77] = https://curl.se/bug/?i=23021 [78] = https://curl.se/bug/?i=22901 [79] = https://curl.se/bug/?i=22902 [80] = https://curl.se/bug/?i=22897 [81] = https://curl.se/bug/?i=22898 [82] = https://curl.se/bug/?i=22905 [83] = https://curl.se/bug/?i=22906 + [84] = https://curl.se/bug/?i=22998 + [85] = https://curl.se/bug/?i=23018 [86] = https://curl.se/bug/?i=22963 [87] = https://curl.se/bug/?i=22899 [88] = https://curl.se/bug/?i=22903 @@ -242,19 +272,41 @@ References to bug reports and discussions on issues: [91] = https://curl.se/bug/?i=22893 [92] = https://curl.se/bug/?i=22888 [93] = https://curl.se/bug/?i=22889 + [94] = https://curl.se/bug/?i=23020 [95] = https://curl.se/bug/?i=22857 [96] = https://curl.se/bug/?i=22982 [97] = https://curl.se/bug/?i=22961 [98] = https://curl.se/bug/?i=22977 + [99] = https://curl.se/bug/?i=23012 [100] = https://curl.se/bug/?i=22969 [101] = https://curl.se/bug/?i=22974 [102] = https://curl.se/bug/?i=22923 [103] = https://curl.se/bug/?i=22917 [104] = https://curl.se/bug/?i=22970 [105] = https://curl.se/bug/?i=22964 + [106] = https://curl.se/bug/?i=23013 + [107] = https://curl.se/bug/?i=23016 [108] = https://curl.se/bug/?i=22916 [109] = https://curl.se/bug/?i=22947 + [110] = https://curl.se/bug/?i=23019 [111] = https://curl.se/bug/?i=22942 [112] = https://curl.se/bug/?i=22941 [113] = https://curl.se/bug/?i=22932 + [114] = https://curl.se/bug/?i=23017 [115] = https://curl.se/bug/?i=12414 + [116] = https://curl.se/bug/?i=23015 + [117] = https://curl.se/bug/?i=23011 + [118] = https://issues.oss-fuzz.com/issues/563679840, https://issues.oss-fuzz.com/issues/559825608 + [119] = https://curl.se/bug/?i=23008 + [120] = https://curl.se/bug/?i=23002 + [121] = https://curl.se/bug/?i=23005 + [122] = https://curl.se/bug/?i=23001 + [124] = https://curl.se/bug/?i=23003 + [126] = https://curl.se/bug/?i=22995 + [127] = https://curl.se/bug/?i=22997 + [128] = https://curl.se/bug/?i=22996 + [129] = https://curl.se/bug/?i=22993 + [131] = https://curl.se/bug/?i=22991 + [132] = https://curl.se/bug/?i=22950 + [137] = https://curl.se/bug/?i=22968 + [138] = https://curl.se/bug/?i=22944 From b8e711a5869a84fda1ad225c680cbab910947921 Mon Sep 17 00:00:00 2001 From: Daniel Stenberg Date: Tue, 22 Sep 2026 07:56:59 +0200 Subject: [PATCH 194/481] os400: restore HAVE_FCHMOD Follow-up to 83b7ad083d and dc874d4369 Reported-by: Charles Muehlberger Closes #23026 --- lib/config-os400.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/config-os400.h b/lib/config-os400.h index 95db6d424b65..d4bce9633177 100644 --- a/lib/config-os400.h +++ b/lib/config-os400.h @@ -55,6 +55,9 @@ /* if you have */ #define HAVE_DIRENT_H +/* Define to 1 if you have the 'fchmod' function. */ +#define HAVE_FCHMOD 1 + /* Define to 1 if you have the fcntl function. */ #define HAVE_FCNTL 1 From 4be7143ba18df3231e37ad4fe855645f24228930 Mon Sep 17 00:00:00 2001 From: Joshua Rogers Date: Tue, 15 Sep 2026 20:36:27 +0200 Subject: [PATCH 195/481] urlapi: normalize legacy numeric IPv4 hosts set via CURLUPART_HOST url_sethost() only ran hostname_check6()/hostname_check() on a value set through curl_url_set(CURLUPART_HOST, ...), unlike parse_authority() which also calls ipv4_normalize() when parsing a full URL. This let legacy numeric IPv4 spellings (e.g. "2130706433", "0177.1", "0x7f.1") pass through CURLUPART_HOST unchanged, while the same input parsed as part of a full URL gets normalized to dotted decimal. Applications that inspect the host via the URL API before connecting could see a different address than what the system resolver later resolves for these forms, since resolvers such as glibc's getaddrinfo() do recognize them. Make url_sethost() call ipv4_normalize() on the host as well, so CURLUPART_HOST produces the same canonical result as the full URL parser, regardless of which API path set the host. The destination dynbuf is sized as (nalloc*3)+1+leadingslash, a cap meant to bound percent-encoding growth. ipv4_normalize() can expand a short legacy-numeric host (e.g. "0" -> "0.0.0.0") past that 3x bound, so for CURLUPART_HOST ensure the initial capacity is at least 16 bytes, enough for the longest possible dotted-decimal result plus NUL. Extends test 1675 accordingly. Reported-by: Joshua Rogers Closes #23027 --- docs/libcurl/curl_url_set.md | 4 +++ lib/urlapi.c | 18 +++++++++- tests/libtest/lib1560.c | 2 +- tests/unit/unit1675.c | 68 ++++++++++++++++++++++++++++++++++++ 4 files changed, 90 insertions(+), 2 deletions(-) diff --git a/docs/libcurl/curl_url_set.md b/docs/libcurl/curl_url_set.md index 35ed109fcd53..b2f818cf063e 100644 --- a/docs/libcurl/curl_url_set.md +++ b/docs/libcurl/curl_url_set.md @@ -131,6 +131,10 @@ you also set the CURLU_URLENCODE flag. Unless *CURLU_NO_AUTHORITY* is set, a blank hostname is not allowed to set. +A numeric IPv4 address (in dotted decimal or a legacy form such as a single +decimal, octal or hexadecimal number) is normalized to dotted decimal, the +same way it is when libcurl parses a full URL. + ## CURLUPART_ZONEID If the hostname is a numeric IPv6 address, this field can also be set. diff --git a/lib/urlapi.c b/lib/urlapi.c index 1c4db04e4638..003ed2f8a1a1 100644 --- a/lib/urlapi.c +++ b/lib/urlapi.c @@ -2056,10 +2056,24 @@ static CURLUcode url_sethost(CURLU *u, struct dynbuf *encp, CURLcode result = Curl_urldecode(newp, n, &decoded, &dlen, REJECT_CTRL); if(result || hostname_check6(u, decoded, dlen)) bad = TRUE; + else { + struct dynbuf dbuf; + curlx_dyn_init(&dbuf, CURL_MAX_INPUT_LENGTH); + if(curlx_dyn_addn(&dbuf, decoded, dlen)) + bad = TRUE; + else if(ipv4_normalize(&dbuf) == HOST_IPV4) { + curlx_dyn_reset(encp); + if(curlx_dyn_addn(encp, curlx_dyn_ptr(&dbuf), curlx_dyn_len(&dbuf))) + bad = TRUE; + } + curlx_dyn_free(&dbuf); + } curlx_free(decoded); } else if(hostname_check6(u, newp, n)) bad = TRUE; + else if(ipv4_normalize(encp) == HOST_ERROR) + bad = TRUE; if(bad) { curlx_dyn_free(encp); return CURLUE_BAD_HOSTNAME; @@ -2143,7 +2157,9 @@ CURLUcode curl_url_set(CURLU *u, CURLUPart what, const char *newp = NULL; struct dynbuf enc; CURLUcode status; - curlx_dyn_init(&enc, (nalloc * 3) + 1 + leadingslash); + curlx_dyn_init(&enc, (what == CURLUPART_HOST) ? + CURLMAX((nalloc * 3) + 1 + leadingslash, 16) : + (nalloc * 3) + 1 + leadingslash); if(leadingslash && (part[0] != '/')) { CURLcode result = curlx_dyn_addn(&enc, "/", 1); diff --git a/tests/libtest/lib1560.c b/tests/libtest/lib1560.c index 03d99ccbbf3b..f045474be3c8 100644 --- a/tests/libtest/lib1560.c +++ b/tests/libtest/lib1560.c @@ -1297,7 +1297,7 @@ static const struct setcase set_parts_list[] = { {"https://example.com/", "host=0xff,", /* '++' there is no automatic URL decode when setting this part */ - "https://0xff/", + "https://0.0.0.255/", 0, /* get */ 0, /* set */ CURLUE_OK, CURLUE_OK}, diff --git a/tests/unit/unit1675.c b/tests/unit/unit1675.c index ca5b8e316b3c..32681a1ae06a 100644 --- a/tests/unit/unit1675.c +++ b/tests/unit/unit1675.c @@ -438,5 +438,73 @@ static CURLcode test_unit1675(const char *arg) abort_if(fails, "parse_hostname_login tests failed"); } + /* Test that CURLUPART_HOST normalizes legacy numeric IPv4 forms the + same way the full URL parser does */ + { + int fails = 0; + unsigned int i; + struct host_test { + const char *host; + const char *out; + }; + static const struct host_test tests[] = { + { "2130706433", "127.0.0.1" }, + { "0177.1", "127.0.0.1" }, + { "0x7f.1", "127.0.0.1" }, + { "0xc0a80001", "192.168.0.1" }, + { "127.0.0.1", "127.0.0.1" }, + { "example.com", "example.com" }, + { "0", "0.0.0.0" }, + { "1", "0.0.0.1" }, + { "7", "0.0.0.7" }, + { "9", "0.0.0.9" }, + { "00", "0.0.0.0" }, + }; + + for(i = 0; i < CURL_ARRAYSIZE(tests); i++) { + CURLU *viaurl = curl_url(); + CURLU *viahost = curl_url(); + CURLUcode uc1, uc2; + char *urlbuf = curl_maprintf("http://%s/", tests[i].host); + char *gothost1 = NULL; + char *gothost2 = NULL; + + if(!viaurl || !viahost || !urlbuf) { + curl_mfprintf(stderr, "%u: failed to allocate memory\n", i); + fails++; + goto host_loop_end; + } + + uc1 = curl_url_set(viaurl, CURLUPART_URL, urlbuf, 0); + if(!uc1) + uc1 = curl_url_get(viaurl, CURLUPART_HOST, &gothost1, 0); + + curl_url_set(viahost, CURLUPART_SCHEME, "http", 0); + uc2 = curl_url_set(viahost, CURLUPART_HOST, tests[i].host, 0); + if(!uc2) + uc2 = curl_url_get(viahost, CURLUPART_HOST, &gothost2, 0); + + if(uc1 || uc2 || + strcmp(gothost1, tests[i].out) || + strcmp(gothost2, tests[i].out)) { + curl_mfprintf(stderr, "host '%s' mismatch: full-URL -> '%s' (%d), " + "CURLUPART_HOST -> '%s' (%d), expected '%s'\n", + tests[i].host, + uc1 ? "error" : gothost1, (int)uc1, + uc2 ? "error" : gothost2, (int)uc2, + tests[i].out); + fails++; + } + +host_loop_end: + curl_free(gothost1); + curl_free(gothost2); + curl_free(urlbuf); + curl_url_cleanup(viaurl); + curl_url_cleanup(viahost); + } + abort_if(fails, "CURLUPART_HOST ipv4 normalization parity tests failed"); + } + UNITTEST_END_SIMPLE } From c3191f80d6ddd98e35ca02ce27c55a941e50890c Mon Sep 17 00:00:00 2001 From: Joshua Rogers Date: Tue, 15 Sep 2026 19:40:23 +0200 Subject: [PATCH 196/481] inet_pton: reject a colon that ends an IPv6 address inet_pton6() committed a hex group when it saw a ':' but never checked what followed it, so a trailing colon after a completed group was silently accepted as if it were not there. This let addresses with an illegal bare trailing colon parse successfully, producing the same binary result as the equivalent address without the stray colon. A colon is only ever valid when followed by another hex digit group or by a second colon forming the "::" compression marker, never by the end of the string. Reject that case at the point the colon is parsed. This also fixes "::1:", a related trailing-colon input that was likewise wrongly accepted for the same underlying reason. Added test cases to unit1961 covering both malformed inputs and a set of valid addresses (::, ::1, 1::, 1::8, 1:2:3:4:5:6:7:8, 1:2:3:4:5:6::8, fe80::1, ::ffff:192.0.2.1) to guard against regressions. Reported-by: Joshua Rogers Closes #23028 --- lib/curlx/inet_pton.c | 2 ++ tests/unit/unit1961.c | 43 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+) diff --git a/lib/curlx/inet_pton.c b/lib/curlx/inet_pton.c index 3b611890f9c7..c2a6eecbb939 100644 --- a/lib/curlx/inet_pton.c +++ b/lib/curlx/inet_pton.c @@ -142,6 +142,8 @@ static int inet_pton6(const char *src, unsigned char *dst) colonp = tp; continue; } + if(!*src) + return 0; if(tp + INT16SZ > endp) return 0; *tp++ = (unsigned char)((val >> 8) & 0xff); diff --git a/tests/unit/unit1961.c b/tests/unit/unit1961.c index 1aa28a25bf9f..ade713435f6e 100644 --- a/tests/unit/unit1961.c +++ b/tests/unit/unit1961.c @@ -144,12 +144,55 @@ static int test_pton(void) return 0; } +struct pton6_case { + const char *addr; + int valid; +}; + +static const struct pton6_case pton6_cases[] = { + { "1:2:3:4:5:6:7:8:", 0 }, /* AISLE-2026-0115-00325 */ + { "::1:", 0 }, /* AISLE-2026-0115-00290 */ + { "1:2:3:", 0 }, + { "1:", 0 }, + { ":1", 0 }, + { ":::", 0 }, + { "1:2:3:4:5:6:7:8::", 0 }, + { "1:2:3:4:5:6:7::8:9", 0 }, + { "", 0 }, + { "::", 1 }, + { "::1", 1 }, + { "1::", 1 }, + { "1::8", 1 }, + { "1:2:3:4:5:6:7:8", 1 }, + { "1:2:3:4:5:6::", 1 }, + { "1:2:3:4:5:6::8", 1 }, + { "fe80::1", 1 }, + { "::ffff:192.0.2.1", 1 }, + { "::127.0.0.1", 1 } +}; + +static int test_pton_cases(void) +{ + unsigned char ipv6a[16]; + size_t i; + + for(i = 0; i < CURL_ARRAYSIZE(pton6_cases); i++) { + int rc = curlx_inet_pton(AF_INET6, pton6_cases[i].addr, ipv6a); + int got_valid = (rc == 1); + if(got_valid != pton6_cases[i].valid) + return 1; /* fail */ + } + + return 0; +} + static CURLcode test_unit1961(const char *arg) { UNITTEST_BEGIN_SIMPLE fail_if(test_ntop(), "curlx_inet_ntop()"); fail_if(test_pton(), "curlx_inet_pton()"); + fail_if(test_pton_cases(), "curlx_inet_pton() IPv6 edge cases"); UNITTEST_END_SIMPLE } From 424d24793e11f6b621de75bfbd674903a22609ba Mon Sep 17 00:00:00 2001 From: Joshua Rogers Date: Tue, 15 Sep 2026 21:02:05 +0200 Subject: [PATCH 197/481] mprintf: use size_t width for the bare Windows %I modifier The bare "%I" length modifier (without an explicit "32" or "64" suffix) is Microsoft's non-standard way to size an integer conversion to the platform's native pointer/size_t width: 4 bytes on 32-bit Windows, 8 bytes on 64-bit Windows. parse_flags() instead based the choice on SIZEOF_CURL_OFF_T, which is 8 on both, so on 32-bit Windows a bare "%In"/"%Id"/"%Iu" was always treated as 8-byte wide. For "%n" this made the MTYPE_INTPTR sink write an 8-byte int64_t through a caller-supplied pointer to a 4-byte destination, corrupting adjacent memory; for "%Id"/"%Iu" it fetched 8 bytes from the argument list where the caller passed a 4-byte value. Switch the bare "%I" case to SIZEOF_SIZE_T, matching the same pattern already used for "%z". Explicit "%I32"/"%I64" are unaffected. curl's own code does not use the bare "%I" modifier anywhere. Added test 1451 for verification (Windows-only). Reported-by: Joshua Rogers Closes #23030 --- lib/mprintf.c | 2 +- tests/data/Makefile.am | 1 + tests/data/test1451 | 22 ++++++++++++ tests/libtest/Makefile.inc | 1 + tests/libtest/lib1451.c | 70 ++++++++++++++++++++++++++++++++++++++ 5 files changed, 95 insertions(+), 1 deletion(-) create mode 100644 tests/data/test1451 create mode 100644 tests/libtest/lib1451.c diff --git a/lib/mprintf.c b/lib/mprintf.c index abe0df4b75ce..1203008bbb05 100644 --- a/lib/mprintf.c +++ b/lib/mprintf.c @@ -252,7 +252,7 @@ static int parse_flags(const char **fmtp, unsigned int *flagsp, int use_dollar, fmt += 2; } else { -#if SIZEOF_CURL_OFF_T > SIZEOF_LONG +#if SIZEOF_SIZE_T > SIZEOF_LONG flags |= FLAGS_LONGLONG; #else flags |= FLAGS_LONG; diff --git a/tests/data/Makefile.am b/tests/data/Makefile.am index 8b18e2ca659a..068cb2aec700 100644 --- a/tests/data/Makefile.am +++ b/tests/data/Makefile.am @@ -1470,6 +1470,7 @@ TESTCASES = \ test1448 \ test1449 \ test1450 \ + test1451 \ test1452 \ test1453 \ test1454 \ diff --git a/tests/data/test1451 b/tests/data/test1451 new file mode 100644 index 000000000000..0ca5c8b160f8 --- /dev/null +++ b/tests/data/test1451 @@ -0,0 +1,22 @@ + + + + +mprintf +libtest + + + +# Client-side + + +win32 + + +lib%TESTNUMBER + + +mprintf %I length modifier unit tests + + + diff --git a/tests/libtest/Makefile.inc b/tests/libtest/Makefile.inc index ad0057bd58aa..2dd93891a50d 100644 --- a/tests/libtest/Makefile.inc +++ b/tests/libtest/Makefile.inc @@ -169,6 +169,7 @@ TESTS_C = \ lib1308.c \ lib1396.c \ lib1398.c \ + lib1451.c \ lib1485.c \ lib1500.c \ lib1501.c \ diff --git a/tests/libtest/lib1451.c b/tests/libtest/lib1451.c new file mode 100644 index 000000000000..240fda5b93d9 --- /dev/null +++ b/tests/libtest/lib1451.c @@ -0,0 +1,70 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ +#include "unitcheck.h" + +#ifdef CURL_HAVE_DIAG +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wformat" +#endif + +static CURLcode test_lib1451(const char *arg) +{ + UNITTEST_BEGIN_SIMPLE + +#ifdef _WIN32 + /* bare "%I" must track native pointer/size_t width, not curl_off_t */ + { + struct { + unsigned char guard1[8]; + size_t count; + unsigned char guard2[8]; + } canary; + unsigned char expect_guard[8]; + char buf[64]; + int rc; + + memset(&canary, 0xaa, sizeof(canary)); + memset(expect_guard, 0xaa, sizeof(expect_guard)); + + rc = curl_msnprintf(buf, sizeof(buf), "0123456789%In", &canary.count); + fail_unless(rc == 10, "curl_msnprintf returned unexpected length"); + fail_unless(canary.count == 10, "%In wrote the wrong count"); + verify_memory(canary.guard1, expect_guard, sizeof(expect_guard)); + verify_memory(canary.guard2, expect_guard, sizeof(expect_guard)); + } + + { + char buf[64]; + curl_msnprintf(buf, sizeof(buf), "%Iu-%d", (size_t)0x11223344, 42); + fail_unless(!strcmp(buf, "287454020-42"), + "%Iu did not consume a size_t-sized argument"); + } +#endif + + UNITTEST_END_SIMPLE +} + +#ifdef CURL_HAVE_DIAG +#pragma GCC diagnostic pop +#endif From 8da875a47b1eecaa5ec23284f154fd5b91f2a904 Mon Sep 17 00:00:00 2001 From: Daniel Stenberg Date: Tue, 22 Sep 2026 10:13:19 +0200 Subject: [PATCH 198/481] tests: invoke lib%TESTNUMBER when using the same tool as test To make them easier to copy and paste from Closes #23033 --- tests/data/test2023 | 2 +- tests/data/test2032 | 2 +- tests/data/test2082 | 2 +- tests/data/test2700 | 2 +- tests/data/test3027 | 2 +- tests/data/test500 | 2 +- tests/data/test520 | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/data/test2023 b/tests/data/test2023 index a57aaeed8ca8..62204eb4950d 100644 --- a/tests/data/test2023 +++ b/tests/data/test2023 @@ -107,7 +107,7 @@ Finally, this is the real page! http -lib2023 +lib%TESTNUMBER diff --git a/tests/data/test2032 b/tests/data/test2032 index 2bddcb5fc0c7..a2c95293235d 100644 --- a/tests/data/test2032 +++ b/tests/data/test2032 @@ -76,7 +76,7 @@ SSL http -lib2032 +lib%TESTNUMBER diff --git a/tests/data/test2082 b/tests/data/test2082 index cf0a28428b68..26b0c175279f 100644 --- a/tests/data/test2082 +++ b/tests/data/test2082 @@ -28,7 +28,7 @@ http Pre-request callback for HTTP -lib2082 +lib%TESTNUMBER diff --git a/tests/data/test2700 b/tests/data/test2700 index 99399dd6c9ac..05a78f9f4c45 100644 --- a/tests/data/test2700 +++ b/tests/data/test2700 @@ -18,7 +18,7 @@ ws http -lib2700 +lib%TESTNUMBER ws://%HOSTIP:%HTTPPORT/%TESTNUMBER diff --git a/tests/data/test3027 b/tests/data/test3027 index d6a79cf7e99f..bc6b94647002 100644 --- a/tests/data/test3027 +++ b/tests/data/test3027 @@ -29,7 +29,7 @@ ftp Get a file via FTP but 550 after MDTM command -lib3027 +lib%TESTNUMBER ftp://%HOSTIP:%FTPPORT/%TESTNUMBER diff --git a/tests/data/test500 b/tests/data/test500 index 077b119ff44d..d4fe0666f4e6 100644 --- a/tests/data/test500 +++ b/tests/data/test500 @@ -31,7 +31,7 @@ http # tool is what to use instead of 'curl' -lib500 +lib%TESTNUMBER diff --git a/tests/data/test520 b/tests/data/test520 index 1ae3607754a0..3c43d8ccc2b4 100644 --- a/tests/data/test520 +++ b/tests/data/test520 @@ -23,7 +23,7 @@ ftp # tool is what to use instead of 'curl' -lib520 +lib%TESTNUMBER From d87b4e4605132aa5f792a5955d648683277e593d Mon Sep 17 00:00:00 2001 From: Daniel Stenberg Date: Tue, 22 Sep 2026 09:50:25 +0200 Subject: [PATCH 199/481] tests: add 'libtest' keyword for libtests And remove unittest from some libtests. Closes #23032 --- tests/data/test1113 | 1 + tests/data/test1114 | 1 + tests/data/test1156 | 1 + tests/data/test1162 | 1 + tests/data/test1163 | 1 + tests/data/test1301 | 1 + tests/data/test1308 | 1 + tests/data/test1396 | 1 + tests/data/test1398 | 1 + tests/data/test1485 | 1 + tests/data/test1500 | 1 + tests/data/test1501 | 1 + tests/data/test1502 | 1 + tests/data/test1503 | 1 + tests/data/test1504 | 1 + tests/data/test1505 | 1 + tests/data/test1506 | 1 + tests/data/test1507 | 1 + tests/data/test1508 | 1 + tests/data/test1509 | 1 + tests/data/test1510 | 1 + tests/data/test1511 | 1 + tests/data/test1512 | 1 + tests/data/test1513 | 1 + tests/data/test1514 | 1 + tests/data/test1515 | 1 + tests/data/test1516 | 1 + tests/data/test1517 | 1 + tests/data/test1518 | 1 + tests/data/test1519 | 1 + tests/data/test1520 | 1 + tests/data/test1521 | 1 + tests/data/test1522 | 1 + tests/data/test1523 | 1 + tests/data/test1525 | 1 + tests/data/test1526 | 1 + tests/data/test1527 | 1 + tests/data/test1528 | 1 + tests/data/test1529 | 1 + tests/data/test1530 | 1 + tests/data/test1531 | 1 + tests/data/test1532 | 1 + tests/data/test1533 | 1 + tests/data/test1534 | 1 + tests/data/test1535 | 1 + tests/data/test1536 | 1 + tests/data/test1537 | 1 + tests/data/test1538 | 1 + tests/data/test1539 | 1 + tests/data/test1540 | 1 + tests/data/test1541 | 1 + tests/data/test1542 | 1 + tests/data/test1543 | 1 + tests/data/test1545 | 1 + tests/data/test1549 | 1 + tests/data/test1550 | 1 + tests/data/test1551 | 1 + tests/data/test1552 | 1 + tests/data/test1553 | 1 + tests/data/test1554 | 1 + tests/data/test1555 | 1 + tests/data/test1556 | 1 + tests/data/test1557 | 1 + tests/data/test1558 | 1 + tests/data/test1559 | 1 + tests/data/test1560 | 2 +- tests/data/test1564 | 1 + tests/data/test1565 | 1 + tests/data/test1567 | 1 + tests/data/test1568 | 1 + tests/data/test1569 | 1 + tests/data/test1570 | 1 + tests/data/test1571 | 1 + tests/data/test1572 | 1 + tests/data/test1573 | 1 + tests/data/test1574 | 1 + tests/data/test1575 | 1 + tests/data/test1576 | 1 + tests/data/test1577 | 1 + tests/data/test1578 | 1 + tests/data/test1579 | 1 + tests/data/test1580 | 1 + tests/data/test1581 | 1 + tests/data/test1582 | 1 + tests/data/test1587 | 1 + tests/data/test1588 | 1 + tests/data/test1589 | 1 + tests/data/test1590 | 1 + tests/data/test1591 | 1 + tests/data/test1592 | 1 + tests/data/test1593 | 1 + tests/data/test1594 | 1 + tests/data/test1595 | 1 + tests/data/test1596 | 1 + tests/data/test1597 | 1 + tests/data/test1598 | 1 + tests/data/test1599 | 1 + tests/data/test1605 | 1 + tests/data/test1616 | 1 + tests/data/test1647 | 1 + tests/data/test1648 | 1 + tests/data/test1649 | 1 + tests/data/test1662 | 1 + tests/data/test1678 | 1 + tests/data/test1686 | 1 + tests/data/test1688 | 1 + tests/data/test1742 | 1 + tests/data/test1743 | 1 + tests/data/test1744 | 1 + tests/data/test1791 | 1 + tests/data/test1900 | 1 + tests/data/test1901 | 1 + tests/data/test1902 | 1 + tests/data/test1903 | 1 + tests/data/test1905 | 1 + tests/data/test1906 | 1 + tests/data/test1907 | 1 + tests/data/test1908 | 1 + tests/data/test1910 | 1 + tests/data/test1911 | 1 + tests/data/test1912 | 1 + tests/data/test1913 | 1 + tests/data/test1914 | 1 + tests/data/test1915 | 1 + tests/data/test1916 | 1 + tests/data/test1917 | 1 + tests/data/test1918 | 1 + tests/data/test1919 | 1 + tests/data/test1920 | 1 + tests/data/test1921 | 1 + tests/data/test1922 | 1 + tests/data/test1933 | 1 + tests/data/test1934 | 1 + tests/data/test1935 | 1 + tests/data/test1936 | 1 + tests/data/test1937 | 1 + tests/data/test1938 | 1 + tests/data/test1939 | 1 + tests/data/test1940 | 1 + tests/data/test1941 | 1 + tests/data/test1942 | 1 + tests/data/test1943 | 1 + tests/data/test1944 | 1 + tests/data/test1945 | 1 + tests/data/test1946 | 1 + tests/data/test1947 | 1 + tests/data/test1948 | 1 + tests/data/test1955 | 1 + tests/data/test1956 | 1 + tests/data/test1957 | 1 + tests/data/test1958 | 1 + tests/data/test1959 | 1 + tests/data/test1960 | 1 + tests/data/test1964 | 1 + tests/data/test1965 | 1 + tests/data/test1967 | 1 + tests/data/test1970 | 1 + tests/data/test1971 | 1 + tests/data/test1972 | 1 + tests/data/test1973 | 1 + tests/data/test1974 | 1 + tests/data/test1975 | 1 + tests/data/test1977 | 1 + tests/data/test1978 | 1 + tests/data/test1985 | 1 + tests/data/test2023 | 1 + tests/data/test2024 | 1 + tests/data/test2025 | 1 + tests/data/test2026 | 1 + tests/data/test2027 | 1 + tests/data/test2028 | 1 + tests/data/test2029 | 1 + tests/data/test2030 | 1 + tests/data/test2031 | 1 + tests/data/test2032 | 1 + tests/data/test2082 | 1 + tests/data/test2083 | 1 + tests/data/test2084 | 1 + tests/data/test2085 | 1 + tests/data/test2086 | 1 + tests/data/test2118 | 1 + tests/data/test2270 | 1 + tests/data/test2301 | 1 + tests/data/test2302 | 1 + tests/data/test2303 | 1 + tests/data/test2304 | 1 + tests/data/test2306 | 1 + tests/data/test2308 | 1 + tests/data/test2309 | 1 + tests/data/test2397 | 1 + tests/data/test2402 | 1 + tests/data/test2404 | 1 + tests/data/test2405 | 1 + tests/data/test2407 | 1 + tests/data/test2412 | 1 + tests/data/test2414 | 1 + tests/data/test2453 | 1 + tests/data/test2502 | 1 + tests/data/test2504 | 1 + tests/data/test2505 | 1 + tests/data/test2506 | 1 + tests/data/test2700 | 1 + tests/data/test2701 | 1 + tests/data/test2702 | 1 + tests/data/test2703 | 1 + tests/data/test2704 | 1 + tests/data/test2705 | 1 + tests/data/test2706 | 1 + tests/data/test2707 | 1 + tests/data/test2708 | 1 + tests/data/test2709 | 1 + tests/data/test2710 | 1 + tests/data/test2711 | 1 + tests/data/test2712 | 1 + tests/data/test2713 | 1 + tests/data/test2714 | 1 + tests/data/test2715 | 1 + tests/data/test2716 | 1 + tests/data/test2717 | 1 + tests/data/test2718 | 1 + tests/data/test2719 | 1 + tests/data/test2720 | 1 + tests/data/test2721 | 1 + tests/data/test2722 | 1 + tests/data/test2723 | 1 + tests/data/test2805 | 1 + tests/data/test2924 | 1 + tests/data/test3010 | 1 + tests/data/test3025 | 1 + tests/data/test3026 | 1 + tests/data/test3027 | 1 + tests/data/test3034 | 1 + tests/data/test3100 | 1 + tests/data/test3101 | 1 + tests/data/test3102 | 1 + tests/data/test3103 | 1 + tests/data/test3104 | 1 + tests/data/test3105 | 1 + tests/data/test3107 | 1 + tests/data/test3207 | 1 + tests/data/test3231 | 1 + tests/data/test3232 | 1 + tests/data/test3233 | 1 + tests/data/test3595 | 1 + tests/data/test3596 | 1 + tests/data/test500 | 1 + tests/data/test5000 | 1 + tests/data/test5004 | 1 + tests/data/test501 | 1 + tests/data/test502 | 1 + tests/data/test503 | 1 + tests/data/test504 | 1 + tests/data/test505 | 1 + tests/data/test506 | 1 + tests/data/test507 | 1 + tests/data/test508 | 1 + tests/data/test509 | 1 + tests/data/test510 | 1 + tests/data/test511 | 1 + tests/data/test512 | 1 + tests/data/test513 | 1 + tests/data/test514 | 1 + tests/data/test515 | 1 + tests/data/test516 | 1 + tests/data/test517 | 2 +- tests/data/test518 | 1 + tests/data/test519 | 1 + tests/data/test520 | 1 + tests/data/test521 | 1 + tests/data/test522 | 1 + tests/data/test523 | 1 + tests/data/test524 | 1 + tests/data/test525 | 1 + tests/data/test526 | 1 + tests/data/test527 | 1 + tests/data/test528 | 1 + tests/data/test529 | 1 + tests/data/test530 | 1 + tests/data/test531 | 1 + tests/data/test532 | 1 + tests/data/test533 | 1 + tests/data/test534 | 1 + tests/data/test535 | 1 + tests/data/test536 | 1 + tests/data/test537 | 1 + tests/data/test538 | 1 + tests/data/test539 | 1 + tests/data/test540 | 1 + tests/data/test541 | 1 + tests/data/test542 | 1 + tests/data/test543 | 1 + tests/data/test544 | 1 + tests/data/test545 | 1 + tests/data/test546 | 1 + tests/data/test547 | 1 + tests/data/test548 | 1 + tests/data/test549 | 1 + tests/data/test550 | 1 + tests/data/test551 | 1 + tests/data/test552 | 1 + tests/data/test553 | 1 + tests/data/test554 | 1 + tests/data/test555 | 1 + tests/data/test556 | 1 + tests/data/test557 | 2 +- tests/data/test558 | 1 + tests/data/test559 | 1 + tests/data/test560 | 1 + tests/data/test561 | 1 + tests/data/test562 | 1 + tests/data/test563 | 1 + tests/data/test564 | 1 + tests/data/test565 | 1 + tests/data/test566 | 1 + tests/data/test567 | 1 + tests/data/test568 | 1 + tests/data/test569 | 1 + tests/data/test570 | 1 + tests/data/test571 | 1 + tests/data/test572 | 1 + tests/data/test573 | 1 + tests/data/test574 | 1 + tests/data/test575 | 1 + tests/data/test576 | 1 + tests/data/test577 | 1 + tests/data/test578 | 1 + tests/data/test579 | 1 + tests/data/test580 | 1 + tests/data/test581 | 1 + tests/data/test582 | 1 + tests/data/test583 | 1 + tests/data/test584 | 1 + tests/data/test585 | 1 + tests/data/test586 | 1 + tests/data/test587 | 1 + tests/data/test588 | 1 + tests/data/test589 | 1 + tests/data/test590 | 1 + tests/data/test591 | 1 + tests/data/test592 | 1 + tests/data/test593 | 1 + tests/data/test594 | 1 + tests/data/test595 | 1 + tests/data/test596 | 1 + tests/data/test597 | 1 + tests/data/test598 | 1 + tests/data/test599 | 1 + tests/data/test643 | 1 + tests/data/test645 | 1 + tests/data/test650 | 1 + tests/data/test651 | 1 + tests/data/test652 | 1 + tests/data/test653 | 1 + tests/data/test654 | 1 + tests/data/test655 | 1 + tests/data/test657 | 1 + tests/data/test658 | 1 + tests/data/test659 | 1 + tests/data/test660 | 1 + tests/data/test661 | 1 + tests/data/test666 | 1 + tests/data/test667 | 1 + tests/data/test668 | 1 + tests/data/test670 | 1 + tests/data/test671 | 1 + tests/data/test672 | 1 + tests/data/test673 | 1 + tests/data/test674 | 1 + tests/data/test676 | 1 + tests/data/test677 | 1 + tests/data/test678 | 1 + tests/data/test689 | 1 + tests/data/test694 | 1 + tests/data/test695 | 1 + tests/data/test696 | 1 + tests/data/test751 | 1 + tests/data/test753 | 1 + tests/data/test757 | 1 + tests/data/test758 | 1 + tests/data/test766 | 1 + 380 files changed, 380 insertions(+), 3 deletions(-) diff --git a/tests/data/test1113 b/tests/data/test1113 index ed606ec667b1..3c3086544dc3 100644 --- a/tests/data/test1113 +++ b/tests/data/test1113 @@ -8,6 +8,7 @@ LIST wildcardmatch ftplistparser flaky +libtest diff --git a/tests/data/test1114 b/tests/data/test1114 index 5391e0c48d3b..d8137d103862 100644 --- a/tests/data/test1114 +++ b/tests/data/test1114 @@ -5,6 +5,7 @@ FTP wildcardmatch ftplistparser +libtest diff --git a/tests/data/test1156 b/tests/data/test1156 index 803feea9b9b1..8b99a817f862 100644 --- a/tests/data/test1156 +++ b/tests/data/test1156 @@ -7,6 +7,7 @@ HTTP GET Content-Range Resume Range +libtest diff --git a/tests/data/test1162 b/tests/data/test1162 index fa0ab768251e..8f242127653e 100644 --- a/tests/data/test1162 +++ b/tests/data/test1162 @@ -8,6 +8,7 @@ LIST wildcardmatch ftplistparser flaky +libtest diff --git a/tests/data/test1163 b/tests/data/test1163 index 1c60bbdbfd3f..19d2b20b7a14 100644 --- a/tests/data/test1163 +++ b/tests/data/test1163 @@ -8,6 +8,7 @@ LIST wildcardmatch ftplistparser flaky +libtest diff --git a/tests/data/test1301 b/tests/data/test1301 index ea8e47c5dcc8..de4f1772b2c8 100644 --- a/tests/data/test1301 +++ b/tests/data/test1301 @@ -3,6 +3,7 @@ curl_strequal +libtest diff --git a/tests/data/test1308 b/tests/data/test1308 index adb471ae821c..ffb924ae9ae8 100644 --- a/tests/data/test1308 +++ b/tests/data/test1308 @@ -5,6 +5,7 @@ curl_formadd curl_formget FORM +libtest diff --git a/tests/data/test1396 b/tests/data/test1396 index 88062c82985c..0ad7fa5a059b 100644 --- a/tests/data/test1396 +++ b/tests/data/test1396 @@ -4,6 +4,7 @@ curl_easy_escape curl_easy_unescape +libtest diff --git a/tests/data/test1398 b/tests/data/test1398 index dde51f699407..30183caae4c0 100644 --- a/tests/data/test1398 +++ b/tests/data/test1398 @@ -3,6 +3,7 @@ curl_msnprintf +libtest diff --git a/tests/data/test1485 b/tests/data/test1485 index 55f9a968f62b..a5d49ffa6276 100644 --- a/tests/data/test1485 +++ b/tests/data/test1485 @@ -4,6 +4,7 @@ HTTP HTTP GET +libtest diff --git a/tests/data/test1500 b/tests/data/test1500 index 6f5bfa034f77..1750d887f948 100644 --- a/tests/data/test1500 +++ b/tests/data/test1500 @@ -5,6 +5,7 @@ HTTP HTTP GET multi +libtest diff --git a/tests/data/test1501 b/tests/data/test1501 index 2c6bd8f1f9c0..991cdc6fd1e5 100644 --- a/tests/data/test1501 +++ b/tests/data/test1501 @@ -7,6 +7,7 @@ RETR multi LIST DELAY +libtest diff --git a/tests/data/test1502 b/tests/data/test1502 index f1004ba06ce1..b2ae4d984969 100644 --- a/tests/data/test1502 +++ b/tests/data/test1502 @@ -6,6 +6,7 @@ HTTP HTTP GET multi CURLOPT_RESOLVE +libtest diff --git a/tests/data/test1503 b/tests/data/test1503 index 758e12d93001..7b4059909884 100644 --- a/tests/data/test1503 +++ b/tests/data/test1503 @@ -6,6 +6,7 @@ HTTP HTTP GET multi CURLOPT_RESOLVE +libtest diff --git a/tests/data/test1504 b/tests/data/test1504 index 35a624db65ed..b592be76cca7 100644 --- a/tests/data/test1504 +++ b/tests/data/test1504 @@ -6,6 +6,7 @@ HTTP HTTP GET multi CURLOPT_RESOLVE +libtest diff --git a/tests/data/test1505 b/tests/data/test1505 index 7925f185bbfc..14015daa5f26 100644 --- a/tests/data/test1505 +++ b/tests/data/test1505 @@ -6,6 +6,7 @@ HTTP HTTP GET multi CURLOPT_RESOLVE +libtest diff --git a/tests/data/test1506 b/tests/data/test1506 index 7801fead66ce..63c84f248be1 100644 --- a/tests/data/test1506 +++ b/tests/data/test1506 @@ -5,6 +5,7 @@ HTTP multi verbose logs +libtest diff --git a/tests/data/test1507 b/tests/data/test1507 index 807793d27a75..cebe1e776849 100644 --- a/tests/data/test1507 +++ b/tests/data/test1507 @@ -4,6 +4,7 @@ SMTP multi +libtest diff --git a/tests/data/test1508 b/tests/data/test1508 index 72f6dee075c9..249a1b900ecd 100644 --- a/tests/data/test1508 +++ b/tests/data/test1508 @@ -4,6 +4,7 @@ HTTP multi +libtest diff --git a/tests/data/test1509 b/tests/data/test1509 index 7e57cd0fad9b..26697b3635df 100644 --- a/tests/data/test1509 +++ b/tests/data/test1509 @@ -7,6 +7,7 @@ HTTP GET HTTP CONNECT HTTP proxy proxytunnel +libtest diff --git a/tests/data/test1510 b/tests/data/test1510 index 90771e6979ee..68a47e62b668 100644 --- a/tests/data/test1510 +++ b/tests/data/test1510 @@ -5,6 +5,7 @@ HTTP verbose logs flaky +libtest diff --git a/tests/data/test1511 b/tests/data/test1511 index ad41ffaaf21a..0a2aa779b530 100644 --- a/tests/data/test1511 +++ b/tests/data/test1511 @@ -4,6 +4,7 @@ HTTP CURLOPT_TIMECONDITION +libtest diff --git a/tests/data/test1512 b/tests/data/test1512 index d62d5c1717d2..4cc606e9f8de 100644 --- a/tests/data/test1512 +++ b/tests/data/test1512 @@ -4,6 +4,7 @@ HTTP GLOBAL DNS CACHE +libtest diff --git a/tests/data/test1513 b/tests/data/test1513 index b84183846df1..e02da3890f44 100644 --- a/tests/data/test1513 +++ b/tests/data/test1513 @@ -4,6 +4,7 @@ PROGRESSFUNCTION DELAY +libtest diff --git a/tests/data/test1514 b/tests/data/test1514 index 0cc190010400..59ed526199e2 100644 --- a/tests/data/test1514 +++ b/tests/data/test1514 @@ -6,6 +6,7 @@ HTTP HTTP POST Content-Length chunked Transfer-Encoding +libtest diff --git a/tests/data/test1515 b/tests/data/test1515 index 2ad5ac0797b8..b9f835566f98 100644 --- a/tests/data/test1515 +++ b/tests/data/test1515 @@ -6,6 +6,7 @@ HTTP multi resolve +libtest diff --git a/tests/data/test1516 b/tests/data/test1516 index 02f2c6d84f17..5dce1737b69b 100644 --- a/tests/data/test1516 +++ b/tests/data/test1516 @@ -6,6 +6,7 @@ HTTP multi resolve +libtest diff --git a/tests/data/test1517 b/tests/data/test1517 index 6b53c3955d00..b853028a6e20 100644 --- a/tests/data/test1517 +++ b/tests/data/test1517 @@ -7,6 +7,7 @@ POST POST callback slow callback early response +libtest # This test checks to make sure curl can call recv() without failing after a diff --git a/tests/data/test1518 b/tests/data/test1518 index 096765e870e7..61a2dcb82878 100644 --- a/tests/data/test1518 +++ b/tests/data/test1518 @@ -3,6 +3,7 @@ HTTP +libtest # This reproduces issue #3340 diff --git a/tests/data/test1519 b/tests/data/test1519 index 7dd225285071..865af37daed8 100644 --- a/tests/data/test1519 +++ b/tests/data/test1519 @@ -3,6 +3,7 @@ HTTP +libtest # This reproduces issue #3340 diff --git a/tests/data/test1520 b/tests/data/test1520 index c26c08866fb2..527983460cf5 100644 --- a/tests/data/test1520 +++ b/tests/data/test1520 @@ -4,6 +4,7 @@ SMTP +libtest diff --git a/tests/data/test1521 b/tests/data/test1521 index e86125b2e755..f7ab06ed0cf3 100644 --- a/tests/data/test1521 +++ b/tests/data/test1521 @@ -3,6 +3,7 @@ curl_easy_setopt +libtest diff --git a/tests/data/test1522 b/tests/data/test1522 index 581ecf6dc969..d25659a1ff4e 100644 --- a/tests/data/test1522 +++ b/tests/data/test1522 @@ -3,6 +3,7 @@ CURLINFO_SIZE_UPLOAD +libtest diff --git a/tests/data/test1523 b/tests/data/test1523 index 6fb4c6b7d038..7668c8f20183 100644 --- a/tests/data/test1523 +++ b/tests/data/test1523 @@ -4,6 +4,7 @@ CURLINFO_LOW_SPEED_LIMIT DELAY +libtest diff --git a/tests/data/test1525 b/tests/data/test1525 index c090feb5d3bb..61cdd74a1d75 100644 --- a/tests/data/test1525 +++ b/tests/data/test1525 @@ -8,6 +8,7 @@ HTTP CONNECT HTTP proxy proxytunnel CURLOPT_PROXYHEADER +libtest diff --git a/tests/data/test1526 b/tests/data/test1526 index 6c78989fa36d..459d3c318ffb 100644 --- a/tests/data/test1526 +++ b/tests/data/test1526 @@ -8,6 +8,7 @@ HTTP CONNECT HTTP proxy proxytunnel CURLOPT_PROXYHEADER +libtest diff --git a/tests/data/test1527 b/tests/data/test1527 index 731aea2ac91e..96b30c22574e 100644 --- a/tests/data/test1527 +++ b/tests/data/test1527 @@ -7,6 +7,7 @@ HTTP PUT HTTP CONNECT HTTP proxy proxytunnel +libtest diff --git a/tests/data/test1528 b/tests/data/test1528 index a2994988bd28..7e276c2f9e0f 100644 --- a/tests/data/test1528 +++ b/tests/data/test1528 @@ -7,6 +7,7 @@ HTTP GET HTTP CONNECT HTTP proxy proxytunnel +libtest diff --git a/tests/data/test1529 b/tests/data/test1529 index f31f70fb4491..3e14d16d38d0 100644 --- a/tests/data/test1529 +++ b/tests/data/test1529 @@ -5,6 +5,7 @@ HTTP HTTP GET HTTP proxy +libtest diff --git a/tests/data/test1530 b/tests/data/test1530 index dba6bce5c5a3..8f23fd1822cf 100644 --- a/tests/data/test1530 +++ b/tests/data/test1530 @@ -3,6 +3,7 @@ CURLOPT_OPENSOCKETFUNCTION +libtest diff --git a/tests/data/test1531 b/tests/data/test1531 index d85ccf49b70f..bcf7fc192364 100644 --- a/tests/data/test1531 +++ b/tests/data/test1531 @@ -3,6 +3,7 @@ CURLOPT_POSTFIELDS +libtest diff --git a/tests/data/test1532 b/tests/data/test1532 index b11938bec131..39a6e6d5f507 100644 --- a/tests/data/test1532 +++ b/tests/data/test1532 @@ -4,6 +4,7 @@ HTTP HTTP GET +libtest diff --git a/tests/data/test1533 b/tests/data/test1533 index 759c237bd47a..ed0093138c9d 100644 --- a/tests/data/test1533 +++ b/tests/data/test1533 @@ -5,6 +5,7 @@ HTTP HTTP POST CURLOPT_KEEP_SENDING_ON_ERROR +libtest diff --git a/tests/data/test1534 b/tests/data/test1534 index 4e534a1ee8c7..540072c01ada 100644 --- a/tests/data/test1534 +++ b/tests/data/test1534 @@ -5,6 +5,7 @@ HTTP HTTP GET CURLINFO_FILETIME +libtest diff --git a/tests/data/test1535 b/tests/data/test1535 index b5805d8915a6..b2ad1e8eff7a 100644 --- a/tests/data/test1535 +++ b/tests/data/test1535 @@ -4,6 +4,7 @@ HTTP HTTP GET +libtest diff --git a/tests/data/test1536 b/tests/data/test1536 index c76e7593a73a..c350d7f620c9 100644 --- a/tests/data/test1536 +++ b/tests/data/test1536 @@ -4,6 +4,7 @@ HTTP HTTP GET +libtest diff --git a/tests/data/test1537 b/tests/data/test1537 index 1c48d3661367..2c4538e92ac2 100644 --- a/tests/data/test1537 +++ b/tests/data/test1537 @@ -3,6 +3,7 @@ URL escape +libtest diff --git a/tests/data/test1538 b/tests/data/test1538 index bdfe9478edd4..c7347ae2973c 100644 --- a/tests/data/test1538 +++ b/tests/data/test1538 @@ -4,6 +4,7 @@ strerror verbose logs +libtest diff --git a/tests/data/test1539 b/tests/data/test1539 index 4a5b8e6d0039..fc7922b6bef0 100644 --- a/tests/data/test1539 +++ b/tests/data/test1539 @@ -6,6 +6,7 @@ HTTP HTTP POST Content-Length chunked Transfer-Encoding +libtest diff --git a/tests/data/test1540 b/tests/data/test1540 index b9b05f3a46d2..e3dff1c1f233 100644 --- a/tests/data/test1540 +++ b/tests/data/test1540 @@ -8,6 +8,7 @@ CURLPAUSE_RECV chunked Transfer-Encoding Trailer: DELAY +libtest diff --git a/tests/data/test1541 b/tests/data/test1541 index 2f030cc904b1..bbbdf7ab3973 100644 --- a/tests/data/test1541 +++ b/tests/data/test1541 @@ -7,6 +7,7 @@ HTTP GET chunked Transfer-Encoding Trailer: DELAY +libtest diff --git a/tests/data/test1542 b/tests/data/test1542 index 2738fbb44068..6e08ae5507a3 100644 --- a/tests/data/test1542 +++ b/tests/data/test1542 @@ -7,6 +7,7 @@ connection reuse persistent connection CURLOPT_MAXLIFETIME_CONN verbose logs +libtest diff --git a/tests/data/test1543 b/tests/data/test1543 index 2433447c6ac0..223bbc183b66 100644 --- a/tests/data/test1543 +++ b/tests/data/test1543 @@ -4,6 +4,7 @@ HTTP CURLOPT_CURLU +libtest # Similar to 1518 but using CURLOPT_CURLU diff --git a/tests/data/test1545 b/tests/data/test1545 index 9007daf2bef4..a5e41f06e5b1 100644 --- a/tests/data/test1545 +++ b/tests/data/test1545 @@ -4,6 +4,7 @@ HTTP HTTP GET +libtest # Server-side diff --git a/tests/data/test1549 b/tests/data/test1549 index 7aaa2d9835d5..c198e8166a4c 100644 --- a/tests/data/test1549 +++ b/tests/data/test1549 @@ -4,6 +4,7 @@ HTTP cookies +libtest diff --git a/tests/data/test1550 b/tests/data/test1550 index 67fe2fafafef..bdc7e6aa7462 100644 --- a/tests/data/test1550 +++ b/tests/data/test1550 @@ -3,6 +3,7 @@ multi +libtest diff --git a/tests/data/test1551 b/tests/data/test1551 index 0f3296847274..259c2b6117f0 100644 --- a/tests/data/test1551 +++ b/tests/data/test1551 @@ -3,6 +3,7 @@ multi +libtest diff --git a/tests/data/test1552 b/tests/data/test1552 index 3b1a57f86f0f..312f9a1cfe69 100644 --- a/tests/data/test1552 +++ b/tests/data/test1552 @@ -5,6 +5,7 @@ IMAP Clear Text FETCH +libtest diff --git a/tests/data/test1553 b/tests/data/test1553 index 84f383085225..0757762bbe53 100644 --- a/tests/data/test1553 +++ b/tests/data/test1553 @@ -5,6 +5,7 @@ IMAP Clear Text FETCH +libtest diff --git a/tests/data/test1554 b/tests/data/test1554 index bc8afb86a447..054fdb59504d 100644 --- a/tests/data/test1554 +++ b/tests/data/test1554 @@ -5,6 +5,7 @@ HTTP HTTP GET shared connections +libtest diff --git a/tests/data/test1555 b/tests/data/test1555 index 1212531686f1..18e453d45dcf 100644 --- a/tests/data/test1555 +++ b/tests/data/test1555 @@ -4,6 +4,7 @@ RECURSIVE_API_CALL DELAY +libtest diff --git a/tests/data/test1556 b/tests/data/test1556 index 30a9c4007edb..4447b4fdc3fe 100644 --- a/tests/data/test1556 +++ b/tests/data/test1556 @@ -4,6 +4,7 @@ HTTP HTTP GET +libtest diff --git a/tests/data/test1557 b/tests/data/test1557 index 9b4ed73fa732..d2db9ddfeaf4 100644 --- a/tests/data/test1557 +++ b/tests/data/test1557 @@ -4,6 +4,7 @@ multi crash +libtest diff --git a/tests/data/test1558 b/tests/data/test1558 index 9070a6dd7177..43a3f672726f 100644 --- a/tests/data/test1558 +++ b/tests/data/test1558 @@ -4,6 +4,7 @@ curl_easy_getinfo CURLINFO_PROTOCOL +libtest diff --git a/tests/data/test1559 b/tests/data/test1559 index 884c490a4c93..b8f037c5e1a2 100644 --- a/tests/data/test1559 +++ b/tests/data/test1559 @@ -4,6 +4,7 @@ CURLOPT_URL verbose logs +libtest diff --git a/tests/data/test1560 b/tests/data/test1560 index a710c8a683b5..8cbe742a35e3 100644 --- a/tests/data/test1560 +++ b/tests/data/test1560 @@ -2,9 +2,9 @@ -unittest urlapi url +libtest diff --git a/tests/data/test1564 b/tests/data/test1564 index 6d9bd745d2b5..838476d1f1b4 100644 --- a/tests/data/test1564 +++ b/tests/data/test1564 @@ -4,6 +4,7 @@ multi wakeup +libtest diff --git a/tests/data/test1565 b/tests/data/test1565 index 78fe7f1f807d..e6b42c4a8a27 100644 --- a/tests/data/test1565 +++ b/tests/data/test1565 @@ -7,6 +7,7 @@ HTTP GET multi multi-threaded wakeup +libtest diff --git a/tests/data/test1567 b/tests/data/test1567 index 86198e3246cf..a017038ae6ca 100644 --- a/tests/data/test1567 +++ b/tests/data/test1567 @@ -6,6 +6,7 @@ HTTP URL API CURLOPT_CURLU CURLOPT_FOLLOWLOCATION +libtest diff --git a/tests/data/test1568 b/tests/data/test1568 index b9b5ddc1770c..780d6650ae5c 100644 --- a/tests/data/test1568 +++ b/tests/data/test1568 @@ -7,6 +7,7 @@ HTTP HTTP GET HTTP Digest auth +libtest diff --git a/tests/data/test1569 b/tests/data/test1569 index faa6968e3887..850ff5984655 100644 --- a/tests/data/test1569 +++ b/tests/data/test1569 @@ -5,6 +5,7 @@ FTP PASV RETR +libtest # Server-side diff --git a/tests/data/test1570 b/tests/data/test1570 index 38b297842bfb..41a0976de911 100644 --- a/tests/data/test1570 +++ b/tests/data/test1570 @@ -5,6 +5,7 @@ FTP PASV RETR +libtest # Server-side diff --git a/tests/data/test1571 b/tests/data/test1571 index 61b9029b1a61..98fccbf0da4c 100644 --- a/tests/data/test1571 +++ b/tests/data/test1571 @@ -4,6 +4,7 @@ CURLFOLLOW_OBEYCODE CURLOPT_FOLLOWLOCATION +libtest # Server-side diff --git a/tests/data/test1572 b/tests/data/test1572 index e63bf49b0794..dfea962eb0e4 100644 --- a/tests/data/test1572 +++ b/tests/data/test1572 @@ -4,6 +4,7 @@ CURLFOLLOW_OBEYCODE CURLOPT_FOLLOWLOCATION +libtest # Server-side diff --git a/tests/data/test1573 b/tests/data/test1573 index 14a6ca956022..452d707acbf5 100644 --- a/tests/data/test1573 +++ b/tests/data/test1573 @@ -4,6 +4,7 @@ CURLFOLLOW_OBEYCODE CURLOPT_FOLLOWLOCATION +libtest # Server-side diff --git a/tests/data/test1574 b/tests/data/test1574 index d0f65be9bffc..72a931bcdc5b 100644 --- a/tests/data/test1574 +++ b/tests/data/test1574 @@ -4,6 +4,7 @@ CURLFOLLOW_FIRSTONLY CURLOPT_FOLLOWLOCATION +libtest # Server-side diff --git a/tests/data/test1575 b/tests/data/test1575 index dc2d86e09f3d..494cb42ae2e6 100644 --- a/tests/data/test1575 +++ b/tests/data/test1575 @@ -4,6 +4,7 @@ CURLFOLLOW_FIRSTONLY CURLOPT_FOLLOWLOCATION +libtest # Server-side diff --git a/tests/data/test1576 b/tests/data/test1576 index c6b395d58fa0..ae30f4ad22d5 100644 --- a/tests/data/test1576 +++ b/tests/data/test1576 @@ -4,6 +4,7 @@ CURLFOLLOW_OBEYCODE CURLOPT_FOLLOWLOCATION +libtest # Server-side diff --git a/tests/data/test1577 b/tests/data/test1577 index ca47483fc649..84c66bd81bc7 100644 --- a/tests/data/test1577 +++ b/tests/data/test1577 @@ -4,6 +4,7 @@ CURLFOLLOW_OBEYCODE CURLOPT_FOLLOWLOCATION +libtest # Server-side diff --git a/tests/data/test1578 b/tests/data/test1578 index 2a48d50fd4fd..260a436d7816 100644 --- a/tests/data/test1578 +++ b/tests/data/test1578 @@ -4,6 +4,7 @@ CURLFOLLOW_FIRSTONLY CURLOPT_FOLLOWLOCATION +libtest # Server-side diff --git a/tests/data/test1579 b/tests/data/test1579 index 29ffc76229ad..e40fe1f6a57d 100644 --- a/tests/data/test1579 +++ b/tests/data/test1579 @@ -4,6 +4,7 @@ CURLFOLLOW_OBEYCODE CURLOPT_FOLLOWLOCATION +libtest # Server-side diff --git a/tests/data/test1580 b/tests/data/test1580 index 4dc10a1f9eff..6ac8d2c136ee 100644 --- a/tests/data/test1580 +++ b/tests/data/test1580 @@ -4,6 +4,7 @@ CURLFOLLOW_FIRSTONLY CURLOPT_FOLLOWLOCATION +libtest # Server-side diff --git a/tests/data/test1581 b/tests/data/test1581 index 458d8c9a5cd6..adb453738e1d 100644 --- a/tests/data/test1581 +++ b/tests/data/test1581 @@ -4,6 +4,7 @@ CURLFOLLOW_OBEYCODE CURLOPT_FOLLOWLOCATION +libtest # Server-side diff --git a/tests/data/test1582 b/tests/data/test1582 index 6c5e55d2298d..bd32a750fabd 100644 --- a/tests/data/test1582 +++ b/tests/data/test1582 @@ -4,6 +4,7 @@ HTTPS GSS-API +libtest diff --git a/tests/data/test1587 b/tests/data/test1587 index 880652793532..9fcb77025f08 100644 --- a/tests/data/test1587 +++ b/tests/data/test1587 @@ -4,6 +4,7 @@ HTTPS CURLINFO_TLS_SESSION +libtest diff --git a/tests/data/test1588 b/tests/data/test1588 index 30ec8ca91282..26ec3cf23a9f 100644 --- a/tests/data/test1588 +++ b/tests/data/test1588 @@ -7,6 +7,7 @@ HTTP GET HTTP proxy HTTP proxy Digest auth multi +libtest diff --git a/tests/data/test1589 b/tests/data/test1589 index 527edb54cab4..22dbfcd2fe7f 100644 --- a/tests/data/test1589 +++ b/tests/data/test1589 @@ -5,6 +5,7 @@ HTTP HTTP proxy HTTP proxy Digest auth +libtest diff --git a/tests/data/test1590 b/tests/data/test1590 index dd660eb30f79..a6f5cdefa8a7 100644 --- a/tests/data/test1590 +++ b/tests/data/test1590 @@ -5,6 +5,7 @@ IMAP Clear Text FETCH +libtest diff --git a/tests/data/test1591 b/tests/data/test1591 index a6f72c3ac124..a2c96109be89 100644 --- a/tests/data/test1591 +++ b/tests/data/test1591 @@ -6,6 +6,7 @@ HTTP HTTP PUT CURLOPT_HTTPTRAILER_FUNCTION CURLOPT_HTTPTRAILER_DATA +libtest diff --git a/tests/data/test1592 b/tests/data/test1592 index 36e7fc8ab104..8d7ff0737c5a 100644 --- a/tests/data/test1592 +++ b/tests/data/test1592 @@ -8,6 +8,7 @@ resolve speedcheck flaky timing-dependent +libtest diff --git a/tests/data/test1593 b/tests/data/test1593 index ef4b21f2d1c4..5e8f130e3bb2 100644 --- a/tests/data/test1593 +++ b/tests/data/test1593 @@ -7,6 +7,7 @@ HTTP GET HTTP replaced headers CURLOPT_TIMECONDITION If-Modified-Since +libtest diff --git a/tests/data/test1594 b/tests/data/test1594 index 8c8b51c56ca4..7cf1a27a96b1 100644 --- a/tests/data/test1594 +++ b/tests/data/test1594 @@ -7,6 +7,7 @@ HTTP GET HTTP replaced headers CURLOPT_TIMECONDITION If-Modified-Since +libtest diff --git a/tests/data/test1595 b/tests/data/test1595 index 996a0cd40974..6e9598ed5383 100644 --- a/tests/data/test1595 +++ b/tests/data/test1595 @@ -7,6 +7,7 @@ HTTP GET HTTP replaced headers CURLOPT_TIMECONDITION If-Modified-Since +libtest diff --git a/tests/data/test1596 b/tests/data/test1596 index 202b5e40f54d..7fe3200a0b7c 100644 --- a/tests/data/test1596 +++ b/tests/data/test1596 @@ -7,6 +7,7 @@ HTTP GET HTTP replaced headers CURLOPT_TIMECONDITION If-Modified-Since +libtest diff --git a/tests/data/test1597 b/tests/data/test1597 index 7f18cf41b748..cc3fd4cd2eac 100644 --- a/tests/data/test1597 +++ b/tests/data/test1597 @@ -3,6 +3,7 @@ CURLOPT_PROTOCOLS_STR +libtest diff --git a/tests/data/test1598 b/tests/data/test1598 index b241677ed67a..58884291843d 100644 --- a/tests/data/test1598 +++ b/tests/data/test1598 @@ -6,6 +6,7 @@ HTTP HTTP POST CURLOPT_HTTPTRAILER_FUNCTION CURLOPT_HTTPTRAILER_DATA +libtest diff --git a/tests/data/test1599 b/tests/data/test1599 index 054a1b68de36..b94c3d3c774c 100644 --- a/tests/data/test1599 +++ b/tests/data/test1599 @@ -4,6 +4,7 @@ FTP netrc +libtest diff --git a/tests/data/test1605 b/tests/data/test1605 index e8edfb2bdca8..34e193c934e6 100644 --- a/tests/data/test1605 +++ b/tests/data/test1605 @@ -3,6 +3,7 @@ curl_easy_escape +libtest diff --git a/tests/data/test1616 b/tests/data/test1616 index 114501b32aa2..716ef132ad0d 100644 --- a/tests/data/test1616 +++ b/tests/data/test1616 @@ -6,6 +6,7 @@ HTTP HTTP GET HTTP proxy HTTP proxy Digest auth +libtest diff --git a/tests/data/test1647 b/tests/data/test1647 index a87487fa9f0c..b49bc6129990 100644 --- a/tests/data/test1647 +++ b/tests/data/test1647 @@ -7,6 +7,7 @@ HTTP GET HTTP proxy HTTP proxy Digest auth multi +libtest diff --git a/tests/data/test1648 b/tests/data/test1648 index 623f3c9a81ae..3be5f4951286 100644 --- a/tests/data/test1648 +++ b/tests/data/test1648 @@ -6,6 +6,7 @@ HTTP HTTP GET HTTP proxy HTTP proxy auth +libtest diff --git a/tests/data/test1649 b/tests/data/test1649 index d2fd7799bd87..b28908f8613b 100644 --- a/tests/data/test1649 +++ b/tests/data/test1649 @@ -4,6 +4,7 @@ HTTP Referer +libtest diff --git a/tests/data/test1662 b/tests/data/test1662 index fce2e8161060..4b8782ca65bb 100644 --- a/tests/data/test1662 +++ b/tests/data/test1662 @@ -4,6 +4,7 @@ HTTP HTTP POST +libtest diff --git a/tests/data/test1678 b/tests/data/test1678 index c0cada0ba79f..72c0d4a81a1f 100644 --- a/tests/data/test1678 +++ b/tests/data/test1678 @@ -3,6 +3,7 @@ ssls-import +libtest diff --git a/tests/data/test1686 b/tests/data/test1686 index 2d419ad60834..4202c35b48b0 100644 --- a/tests/data/test1686 +++ b/tests/data/test1686 @@ -4,6 +4,7 @@ HTTP Digest +libtest diff --git a/tests/data/test1688 b/tests/data/test1688 index 357591baa268..256b81373e63 100644 --- a/tests/data/test1688 +++ b/tests/data/test1688 @@ -4,6 +4,7 @@ RTSP OPTIONS +libtest diff --git a/tests/data/test1742 b/tests/data/test1742 index fa82f1bde29a..823cc67ef627 100644 --- a/tests/data/test1742 +++ b/tests/data/test1742 @@ -5,6 +5,7 @@ HTTP HTTP GET SOCKS5 +libtest diff --git a/tests/data/test1743 b/tests/data/test1743 index 6773b7e2ebcb..beb430356bf6 100644 --- a/tests/data/test1743 +++ b/tests/data/test1743 @@ -5,6 +5,7 @@ HTTP HTTP GET SOCKS5 +libtest diff --git a/tests/data/test1744 b/tests/data/test1744 index 777571edb33c..ff51ac89f90d 100644 --- a/tests/data/test1744 +++ b/tests/data/test1744 @@ -5,6 +5,7 @@ HTTP HTTP GET SOCKS5 +libtest diff --git a/tests/data/test1791 b/tests/data/test1791 index 1cbe039007bb..f035833b891b 100644 --- a/tests/data/test1791 +++ b/tests/data/test1791 @@ -4,6 +4,7 @@ CURLOPT_POSTFIELDSIZE_LARGE duphandle +libtest diff --git a/tests/data/test1900 b/tests/data/test1900 index 08eb8f304d6e..0c7d7984362b 100644 --- a/tests/data/test1900 +++ b/tests/data/test1900 @@ -4,6 +4,7 @@ HTTP HSTS +libtest diff --git a/tests/data/test1901 b/tests/data/test1901 index e1e356dd8e52..0fa2d0c7ab4d 100644 --- a/tests/data/test1901 +++ b/tests/data/test1901 @@ -5,6 +5,7 @@ HTTP HTTP POST CURLOPT_READFUNCTION +libtest diff --git a/tests/data/test1902 b/tests/data/test1902 index a794fd4e58e7..5c826bd8a731 100644 --- a/tests/data/test1902 +++ b/tests/data/test1902 @@ -3,6 +3,7 @@ cookies +libtest diff --git a/tests/data/test1903 b/tests/data/test1903 index 788197742126..54c94c10dc8a 100644 --- a/tests/data/test1903 +++ b/tests/data/test1903 @@ -5,6 +5,7 @@ HTTP cookies CURLOPT_COOKIEFILE +libtest diff --git a/tests/data/test1905 b/tests/data/test1905 index c47ccee00b99..09685672c377 100644 --- a/tests/data/test1905 +++ b/tests/data/test1905 @@ -5,6 +5,7 @@ HTTP cookies FLUSH +libtest diff --git a/tests/data/test1906 b/tests/data/test1906 index cd2bfd93d7a7..0cac8b52e65c 100644 --- a/tests/data/test1906 +++ b/tests/data/test1906 @@ -4,6 +4,7 @@ CURLOPT_CURLU CURLOPT_PORT +libtest diff --git a/tests/data/test1907 b/tests/data/test1907 index aa6fb205abe4..01c7ec2dd8f6 100644 --- a/tests/data/test1907 +++ b/tests/data/test1907 @@ -3,6 +3,7 @@ CURLINFO_EFFECTIVE_URL +libtest diff --git a/tests/data/test1908 b/tests/data/test1908 index bd564ae20089..fb51bf20dde6 100644 --- a/tests/data/test1908 +++ b/tests/data/test1908 @@ -3,6 +3,7 @@ CURLINFO_EFFECTIVE_URL +libtest diff --git a/tests/data/test1910 b/tests/data/test1910 index afd40bac1df4..9994f3f33ca8 100644 --- a/tests/data/test1910 +++ b/tests/data/test1910 @@ -5,6 +5,7 @@ HTTP HTTP Basic CURLOPT_FOLLOWLOCATION +libtest diff --git a/tests/data/test1911 b/tests/data/test1911 index 8ba5c1ab3f81..3b90faaed479 100644 --- a/tests/data/test1911 +++ b/tests/data/test1911 @@ -3,6 +3,7 @@ curl_easy_option +libtest diff --git a/tests/data/test1912 b/tests/data/test1912 index 86775e1ee3ac..326a3bc87719 100644 --- a/tests/data/test1912 +++ b/tests/data/test1912 @@ -4,6 +4,7 @@ curl_easy_option typecheck +libtest diff --git a/tests/data/test1913 b/tests/data/test1913 index c3624de8a580..ebab77dd9c0c 100644 --- a/tests/data/test1913 +++ b/tests/data/test1913 @@ -4,6 +4,7 @@ FTP CURLOPT_NOBODY +libtest diff --git a/tests/data/test1914 b/tests/data/test1914 index 92dc657dc32a..54b569d78eb4 100644 --- a/tests/data/test1914 +++ b/tests/data/test1914 @@ -5,6 +5,7 @@ FTP CURLOPT_NOBODY CURLOPT_FILETIME +libtest diff --git a/tests/data/test1915 b/tests/data/test1915 index 8f9089b2946d..4d534deeb3c3 100644 --- a/tests/data/test1915 +++ b/tests/data/test1915 @@ -5,6 +5,7 @@ HTTP HSTS CURLOPT_HSTSREADFUNCTION +libtest diff --git a/tests/data/test1916 b/tests/data/test1916 index 0a56251d2478..49ee85ae9844 100644 --- a/tests/data/test1916 +++ b/tests/data/test1916 @@ -4,6 +4,7 @@ MQTT MQTT PUBLISH +libtest diff --git a/tests/data/test1917 b/tests/data/test1917 index d1a79d2d9b1a..a7417b7125cc 100644 --- a/tests/data/test1917 +++ b/tests/data/test1917 @@ -4,6 +4,7 @@ MQTT MQTT PUBLISH +libtest diff --git a/tests/data/test1918 b/tests/data/test1918 index fda890822432..15d66219092d 100644 --- a/tests/data/test1918 +++ b/tests/data/test1918 @@ -5,6 +5,7 @@ curl_easy_option curl_easy_option_by_name curl_easy_option_by_id +libtest diff --git a/tests/data/test1919 b/tests/data/test1919 index c18962e73079..9829b25864a3 100644 --- a/tests/data/test1919 +++ b/tests/data/test1919 @@ -3,6 +3,7 @@ connection reuse +libtest diff --git a/tests/data/test1920 b/tests/data/test1920 index 3471025b416f..901a5a3b5791 100644 --- a/tests/data/test1920 +++ b/tests/data/test1920 @@ -3,6 +3,7 @@ cookies +libtest diff --git a/tests/data/test1921 b/tests/data/test1921 index 15a3fe1ae7b9..50018f6caa7a 100644 --- a/tests/data/test1921 +++ b/tests/data/test1921 @@ -3,6 +3,7 @@ urlapi +libtest diff --git a/tests/data/test1922 b/tests/data/test1922 index 9c35ccff6f18..64e5e803faf0 100644 --- a/tests/data/test1922 +++ b/tests/data/test1922 @@ -6,6 +6,7 @@ HTTP HTTP proxy HSTS curl_easy_duphandle +libtest diff --git a/tests/data/test1933 b/tests/data/test1933 index cda12d19a121..0a9b4aa3d886 100644 --- a/tests/data/test1933 +++ b/tests/data/test1933 @@ -5,6 +5,7 @@ HTTP CURLOPT_AWS_SIGV4 aws-sigv4 +libtest diff --git a/tests/data/test1934 b/tests/data/test1934 index 9c52a0d0bddd..bec4c2acff7b 100644 --- a/tests/data/test1934 +++ b/tests/data/test1934 @@ -5,6 +5,7 @@ HTTP CURLOPT_AWS_SIGV4 aws-sigv4 +libtest diff --git a/tests/data/test1935 b/tests/data/test1935 index f92c7e8ea2d3..d7e092a04c2c 100644 --- a/tests/data/test1935 +++ b/tests/data/test1935 @@ -5,6 +5,7 @@ HTTP CURLOPT_AWS_SIGV4 aws-sigv4 +libtest diff --git a/tests/data/test1936 b/tests/data/test1936 index e43eb0be6916..2f634cff2449 100644 --- a/tests/data/test1936 +++ b/tests/data/test1936 @@ -5,6 +5,7 @@ HTTP CURLOPT_AWS_SIGV4 aws-sigv4 +libtest diff --git a/tests/data/test1937 b/tests/data/test1937 index 2043053e59db..b5e5ab04dba8 100644 --- a/tests/data/test1937 +++ b/tests/data/test1937 @@ -6,6 +6,7 @@ HTTP HTTP POST CURLOPT_AWS_SIGV4 aws-sigv4 +libtest diff --git a/tests/data/test1938 b/tests/data/test1938 index a2ea20fc9662..8e0c66403ff6 100644 --- a/tests/data/test1938 +++ b/tests/data/test1938 @@ -6,6 +6,7 @@ HTTP HTTP POST CURLOPT_AWS_SIGV4 aws-sigv4 +libtest diff --git a/tests/data/test1939 b/tests/data/test1939 index d94a9f7c5c89..248a3dc2fea9 100644 --- a/tests/data/test1939 +++ b/tests/data/test1939 @@ -4,6 +4,7 @@ CONNECT curl_easy_cleanup +libtest diff --git a/tests/data/test1940 b/tests/data/test1940 index e8f7d44e9594..9a9443c5f99f 100644 --- a/tests/data/test1940 +++ b/tests/data/test1940 @@ -3,6 +3,7 @@ curl_easy_header +libtest diff --git a/tests/data/test1941 b/tests/data/test1941 index 4532403bc9fd..5c2796df5553 100644 --- a/tests/data/test1941 +++ b/tests/data/test1941 @@ -4,6 +4,7 @@ curl_easy_header CONNECT +libtest diff --git a/tests/data/test1942 b/tests/data/test1942 index 2dd1968ef6c8..d125c69f1bbb 100644 --- a/tests/data/test1942 +++ b/tests/data/test1942 @@ -4,6 +4,7 @@ curl_easy_header CONNECT +libtest diff --git a/tests/data/test1943 b/tests/data/test1943 index 8cae4eb01fdf..a6e01c6e084a 100644 --- a/tests/data/test1943 +++ b/tests/data/test1943 @@ -4,6 +4,7 @@ curl_easy_header CONNECT +libtest diff --git a/tests/data/test1944 b/tests/data/test1944 index 1b0dd469c156..6d73cc9a0787 100644 --- a/tests/data/test1944 +++ b/tests/data/test1944 @@ -3,6 +3,7 @@ curl_easy_header +libtest diff --git a/tests/data/test1945 b/tests/data/test1945 index e788528ba997..0ed91e581861 100644 --- a/tests/data/test1945 +++ b/tests/data/test1945 @@ -4,6 +4,7 @@ curl_easy_header CONNECT +libtest diff --git a/tests/data/test1946 b/tests/data/test1946 index 571fbc7fc3ab..5931522459de 100644 --- a/tests/data/test1946 +++ b/tests/data/test1946 @@ -3,6 +3,7 @@ curl_easy_header +libtest diff --git a/tests/data/test1947 b/tests/data/test1947 index 9519809c586d..27d150675637 100644 --- a/tests/data/test1947 +++ b/tests/data/test1947 @@ -3,6 +3,7 @@ curl_easy_nextheader +libtest diff --git a/tests/data/test1948 b/tests/data/test1948 index ac926c0057f6..907ce1dbacfa 100644 --- a/tests/data/test1948 +++ b/tests/data/test1948 @@ -5,6 +5,7 @@ HTTP HTTP POST HTTP PUT +libtest diff --git a/tests/data/test1955 b/tests/data/test1955 index 82349c25395b..28251981f08b 100644 --- a/tests/data/test1955 +++ b/tests/data/test1955 @@ -5,6 +5,7 @@ HTTP CURLOPT_AWS_SIGV4 aws-sigv4 +libtest diff --git a/tests/data/test1956 b/tests/data/test1956 index f3ad8b859a14..161dea760fb4 100644 --- a/tests/data/test1956 +++ b/tests/data/test1956 @@ -5,6 +5,7 @@ HTTP CURLOPT_AWS_SIGV4 aws-sigv4 +libtest diff --git a/tests/data/test1957 b/tests/data/test1957 index 77b48b238897..6cb1eab55129 100644 --- a/tests/data/test1957 +++ b/tests/data/test1957 @@ -5,6 +5,7 @@ HTTP CURLOPT_AWS_SIGV4 aws-sigv4 +libtest diff --git a/tests/data/test1958 b/tests/data/test1958 index 672adfec1f77..73713ddcae06 100644 --- a/tests/data/test1958 +++ b/tests/data/test1958 @@ -4,6 +4,7 @@ HTTP CURLOPT_AWS_SIGV4 +libtest diff --git a/tests/data/test1959 b/tests/data/test1959 index 27a778c305ac..3314fe7e4280 100644 --- a/tests/data/test1959 +++ b/tests/data/test1959 @@ -5,6 +5,7 @@ HTTP CURLOPT_AWS_SIGV4 aws-sigv4 +libtest diff --git a/tests/data/test1960 b/tests/data/test1960 index d8d8fd6671f7..d1a06282b070 100644 --- a/tests/data/test1960 +++ b/tests/data/test1960 @@ -5,6 +5,7 @@ HTTP CURLOPT_SOCKOPTFUNCTION CURL_SOCKOPT_ALREADY_CONNECTED +libtest diff --git a/tests/data/test1964 b/tests/data/test1964 index fc636308fbb3..ddf5f291740e 100644 --- a/tests/data/test1964 +++ b/tests/data/test1964 @@ -4,6 +4,7 @@ HTTP CURLOPT_AWS_SIGV4 +libtest diff --git a/tests/data/test1965 b/tests/data/test1965 index 195ce9c5cf9a..645cb03fafb4 100644 --- a/tests/data/test1965 +++ b/tests/data/test1965 @@ -4,6 +4,7 @@ urlapi curl_url_set +libtest diff --git a/tests/data/test1967 b/tests/data/test1967 index c012a70ba63c..18fb2c4a3c87 100644 --- a/tests/data/test1967 +++ b/tests/data/test1967 @@ -4,6 +4,7 @@ HTTP urlapi +libtest diff --git a/tests/data/test1970 b/tests/data/test1970 index 8dae28c75339..42b10aa9c23f 100644 --- a/tests/data/test1970 +++ b/tests/data/test1970 @@ -5,6 +5,7 @@ HTTP CURLOPT_AWS_SIGV4 aws-sigv4 +libtest diff --git a/tests/data/test1971 b/tests/data/test1971 index c303018c72df..d52343b9dde6 100644 --- a/tests/data/test1971 +++ b/tests/data/test1971 @@ -5,6 +5,7 @@ HTTP CURLOPT_AWS_SIGV4 aws-sigv4 +libtest diff --git a/tests/data/test1972 b/tests/data/test1972 index d128dccf452c..451a6ec72aca 100644 --- a/tests/data/test1972 +++ b/tests/data/test1972 @@ -5,6 +5,7 @@ HTTP CURLOPT_AWS_SIGV4 aws-sigv4 +libtest diff --git a/tests/data/test1973 b/tests/data/test1973 index c2527df686f4..31636f9f3f0b 100644 --- a/tests/data/test1973 +++ b/tests/data/test1973 @@ -5,6 +5,7 @@ HTTP CURLOPT_AWS_SIGV4 aws-sigv4 +libtest diff --git a/tests/data/test1974 b/tests/data/test1974 index 20a10a87056d..789da05df7d9 100644 --- a/tests/data/test1974 +++ b/tests/data/test1974 @@ -5,6 +5,7 @@ HTTP CURLOPT_AWS_SIGV4 aws-sigv4 +libtest diff --git a/tests/data/test1975 b/tests/data/test1975 index e5d6272eda0b..ac9312da60e7 100644 --- a/tests/data/test1975 +++ b/tests/data/test1975 @@ -5,6 +5,7 @@ HTTP CURLOPT_AWS_SIGV4 aws-sigv4 +libtest diff --git a/tests/data/test1977 b/tests/data/test1977 index bcd6ed31d445..f9b9dd820c32 100644 --- a/tests/data/test1977 +++ b/tests/data/test1977 @@ -4,6 +4,7 @@ CURLOPT_CURLU CURLINFO_EFFECTIVE_URL +libtest diff --git a/tests/data/test1978 b/tests/data/test1978 index 4113900fca53..9e3d0f1586fd 100644 --- a/tests/data/test1978 +++ b/tests/data/test1978 @@ -5,6 +5,7 @@ HTTP CURLOPT_AWS_SIGV4 aws-sigv4 +libtest diff --git a/tests/data/test1985 b/tests/data/test1985 index 78be4df0ea51..01e3d0564b5a 100644 --- a/tests/data/test1985 +++ b/tests/data/test1985 @@ -3,6 +3,7 @@ Digest +libtest diff --git a/tests/data/test2023 b/tests/data/test2023 index 62204eb4950d..062812bc6930 100644 --- a/tests/data/test2023 +++ b/tests/data/test2023 @@ -5,6 +5,7 @@ HTTP HTTP GET HTTP Basic auth +libtest # Server-side diff --git a/tests/data/test2024 b/tests/data/test2024 index af60388ba7dc..dd0c24b287e3 100644 --- a/tests/data/test2024 +++ b/tests/data/test2024 @@ -6,6 +6,7 @@ HTTP HTTP GET HTTP Basic auth HTTP Digest auth +libtest # Server-side diff --git a/tests/data/test2025 b/tests/data/test2025 index 3d1db3f4e519..d4359e414237 100644 --- a/tests/data/test2025 +++ b/tests/data/test2025 @@ -7,6 +7,7 @@ HTTP GET HTTP Basic auth HTTP NTLM auth NTLM +libtest # Server-side diff --git a/tests/data/test2026 b/tests/data/test2026 index 9fc1c6bb9625..5bdbf877b826 100644 --- a/tests/data/test2026 +++ b/tests/data/test2026 @@ -6,6 +6,7 @@ HTTP HTTP GET HTTP Basic auth HTTP Digest auth +libtest # Server-side diff --git a/tests/data/test2027 b/tests/data/test2027 index dfcefa302a16..0d9b1c826ce6 100644 --- a/tests/data/test2027 +++ b/tests/data/test2027 @@ -5,6 +5,7 @@ HTTP HTTP GET HTTP Digest auth +libtest # Server-side diff --git a/tests/data/test2028 b/tests/data/test2028 index f61d2a642ca9..aabc5fc5f01a 100644 --- a/tests/data/test2028 +++ b/tests/data/test2028 @@ -7,6 +7,7 @@ HTTP GET HTTP Digest auth HTTP NTLM auth NTLM +libtest # Server-side diff --git a/tests/data/test2029 b/tests/data/test2029 index 361aa7cb6b84..beebdcbe314a 100644 --- a/tests/data/test2029 +++ b/tests/data/test2029 @@ -7,6 +7,7 @@ HTTP GET HTTP Basic auth HTTP NTLM auth NTLM +libtest # Server-side diff --git a/tests/data/test2030 b/tests/data/test2030 index 5027fd45da4b..bb43567628c5 100644 --- a/tests/data/test2030 +++ b/tests/data/test2030 @@ -7,6 +7,7 @@ HTTP GET HTTP Digest auth HTTP NTLM auth NTLM +libtest # Server-side diff --git a/tests/data/test2031 b/tests/data/test2031 index 09e1f7734dfc..deae884120e9 100644 --- a/tests/data/test2031 +++ b/tests/data/test2031 @@ -6,6 +6,7 @@ HTTP HTTP GET HTTP NTLM auth NTLM +libtest # Server-side diff --git a/tests/data/test2032 b/tests/data/test2032 index a2c95293235d..b5fa45a4ab41 100644 --- a/tests/data/test2032 +++ b/tests/data/test2032 @@ -9,6 +9,7 @@ HTTP NTLM auth NTLM flaky timing-dependent +libtest # Server-side diff --git a/tests/data/test2082 b/tests/data/test2082 index 26b0c175279f..2ec215ddfe2f 100644 --- a/tests/data/test2082 +++ b/tests/data/test2082 @@ -3,6 +3,7 @@ HTTP +libtest diff --git a/tests/data/test2083 b/tests/data/test2083 index 2bd93f1ac743..9d2c2a067a9d 100644 --- a/tests/data/test2083 +++ b/tests/data/test2083 @@ -3,6 +3,7 @@ FTP +libtest diff --git a/tests/data/test2084 b/tests/data/test2084 index 4ac5fbee75d1..ffd19d237681 100644 --- a/tests/data/test2084 +++ b/tests/data/test2084 @@ -3,6 +3,7 @@ HTTP +libtest diff --git a/tests/data/test2085 b/tests/data/test2085 index 5f26b50cfa92..c74616ee38c5 100644 --- a/tests/data/test2085 +++ b/tests/data/test2085 @@ -4,6 +4,7 @@ HTTP followlocation +libtest diff --git a/tests/data/test2086 b/tests/data/test2086 index 5336ddaba628..9cdac32bdd58 100644 --- a/tests/data/test2086 +++ b/tests/data/test2086 @@ -4,6 +4,7 @@ HTTP IPv6 +libtest diff --git a/tests/data/test2118 b/tests/data/test2118 index a3a18e750198..7e795e535f30 100644 --- a/tests/data/test2118 +++ b/tests/data/test2118 @@ -5,6 +5,7 @@ HTTPS HTTP GET PEM certificate +libtest diff --git a/tests/data/test2270 b/tests/data/test2270 index 7e6f7f5ad431..5fd59a100b07 100644 --- a/tests/data/test2270 +++ b/tests/data/test2270 @@ -6,6 +6,7 @@ HTTP HTTP POST HTTP MIME Resume +libtest diff --git a/tests/data/test2301 b/tests/data/test2301 index 4767063105ca..c6f120f226ec 100644 --- a/tests/data/test2301 +++ b/tests/data/test2301 @@ -3,6 +3,7 @@ WebSockets +libtest diff --git a/tests/data/test2302 b/tests/data/test2302 index 396ba16a7ecc..02df3884686b 100644 --- a/tests/data/test2302 +++ b/tests/data/test2302 @@ -3,6 +3,7 @@ WebSockets +libtest diff --git a/tests/data/test2303 b/tests/data/test2303 index 884123dfba7c..6c3cae335635 100644 --- a/tests/data/test2303 +++ b/tests/data/test2303 @@ -3,6 +3,7 @@ WebSockets +libtest diff --git a/tests/data/test2304 b/tests/data/test2304 index aab6dc822b48..19ca6547af01 100644 --- a/tests/data/test2304 +++ b/tests/data/test2304 @@ -3,6 +3,7 @@ WebSockets +libtest diff --git a/tests/data/test2306 b/tests/data/test2306 index eec01e97106d..eec63c1ae5c4 100644 --- a/tests/data/test2306 +++ b/tests/data/test2306 @@ -4,6 +4,7 @@ HTTP HTTP GET +libtest # This reproduces the #11101 issue, when the second response comes back diff --git a/tests/data/test2308 b/tests/data/test2308 index a3770ca5a8ed..68677e041416 100644 --- a/tests/data/test2308 +++ b/tests/data/test2308 @@ -4,6 +4,7 @@ HTTP HTTP GET +libtest # This reproduces the #13669 issue diff --git a/tests/data/test2309 b/tests/data/test2309 index 1549787b0da1..6e5dbd3e1d00 100644 --- a/tests/data/test2309 +++ b/tests/data/test2309 @@ -4,6 +4,7 @@ netrc HTTP +libtest # Server-side diff --git a/tests/data/test2397 b/tests/data/test2397 index 1a0996febb38..444e804595e0 100644 --- a/tests/data/test2397 +++ b/tests/data/test2397 @@ -4,6 +4,7 @@ CURLINFO_REFERER CURLOPT_REFERER +libtest diff --git a/tests/data/test2402 b/tests/data/test2402 index 269bc4b1006a..7a2ec6f4a580 100644 --- a/tests/data/test2402 +++ b/tests/data/test2402 @@ -6,6 +6,7 @@ HTTP HTTP/2 multi verbose logs +libtest diff --git a/tests/data/test2404 b/tests/data/test2404 index 101290cf7529..5a6b6a41fc4c 100644 --- a/tests/data/test2404 +++ b/tests/data/test2404 @@ -6,6 +6,7 @@ HTTP HTTP/2 multi verbose logs +libtest diff --git a/tests/data/test2405 b/tests/data/test2405 index 6ccb6ba394d4..06f659a3f687 100644 --- a/tests/data/test2405 +++ b/tests/data/test2405 @@ -4,6 +4,7 @@ multi HTTP +libtest diff --git a/tests/data/test2407 b/tests/data/test2407 index b9f17bb22fe1..cd0e6fce606e 100644 --- a/tests/data/test2407 +++ b/tests/data/test2407 @@ -5,6 +5,7 @@ multi HTTP HTTP/2 +libtest diff --git a/tests/data/test2412 b/tests/data/test2412 index e0320e2ce4b6..9d67a282a952 100644 --- a/tests/data/test2412 +++ b/tests/data/test2412 @@ -3,6 +3,7 @@ multi +libtest diff --git a/tests/data/test2414 b/tests/data/test2414 index 197beffe8f9c..7c227cab27a3 100644 --- a/tests/data/test2414 +++ b/tests/data/test2414 @@ -4,6 +4,7 @@ multi wakeup +libtest diff --git a/tests/data/test2453 b/tests/data/test2453 index 8456b9e95c9a..6fc757a06d37 100644 --- a/tests/data/test2453 +++ b/tests/data/test2453 @@ -6,6 +6,7 @@ HTTP HTTP GET CURLMINFO_XFERS_DONE curl_multi_info_read +libtest diff --git a/tests/data/test2502 b/tests/data/test2502 index ad55c553f9bc..2cf16a2d2bb7 100644 --- a/tests/data/test2502 +++ b/tests/data/test2502 @@ -6,6 +6,7 @@ HTTP HTTP/3 multi verbose logs +libtest diff --git a/tests/data/test2504 b/tests/data/test2504 index 7bb4a81ed7e6..97f254081502 100644 --- a/tests/data/test2504 +++ b/tests/data/test2504 @@ -4,6 +4,7 @@ HTTP cookies +libtest diff --git a/tests/data/test2505 b/tests/data/test2505 index 8fac590b3706..217b3033e224 100644 --- a/tests/data/test2505 +++ b/tests/data/test2505 @@ -5,6 +5,7 @@ HTTP referer autoreferer +libtest diff --git a/tests/data/test2506 b/tests/data/test2506 index 9c65002496e8..7e79bcc5a63e 100644 --- a/tests/data/test2506 +++ b/tests/data/test2506 @@ -4,6 +4,7 @@ HTTP cookies +libtest diff --git a/tests/data/test2700 b/tests/data/test2700 index 05a78f9f4c45..8a875b682be6 100644 --- a/tests/data/test2700 +++ b/tests/data/test2700 @@ -3,6 +3,7 @@ WebSockets +libtest diff --git a/tests/data/test2701 b/tests/data/test2701 index c413c7ab91b9..de33e7004300 100644 --- a/tests/data/test2701 +++ b/tests/data/test2701 @@ -3,6 +3,7 @@ WebSockets +libtest diff --git a/tests/data/test2702 b/tests/data/test2702 index 989d1dfa77a9..55c1a214e8aa 100644 --- a/tests/data/test2702 +++ b/tests/data/test2702 @@ -3,6 +3,7 @@ WebSockets +libtest diff --git a/tests/data/test2703 b/tests/data/test2703 index f39d91ecb41e..4d26dbdb0fa6 100644 --- a/tests/data/test2703 +++ b/tests/data/test2703 @@ -3,6 +3,7 @@ WebSockets +libtest diff --git a/tests/data/test2704 b/tests/data/test2704 index f29699f4e065..3541d1f1594e 100644 --- a/tests/data/test2704 +++ b/tests/data/test2704 @@ -3,6 +3,7 @@ WebSockets +libtest diff --git a/tests/data/test2705 b/tests/data/test2705 index f0cb84346007..035da57ccbfe 100644 --- a/tests/data/test2705 +++ b/tests/data/test2705 @@ -3,6 +3,7 @@ WebSockets +libtest diff --git a/tests/data/test2706 b/tests/data/test2706 index 38c196f27e3b..3d10c7dca0a7 100644 --- a/tests/data/test2706 +++ b/tests/data/test2706 @@ -3,6 +3,7 @@ WebSockets +libtest diff --git a/tests/data/test2707 b/tests/data/test2707 index 6e85911c2a8e..d6878c694a38 100644 --- a/tests/data/test2707 +++ b/tests/data/test2707 @@ -3,6 +3,7 @@ WebSockets +libtest diff --git a/tests/data/test2708 b/tests/data/test2708 index 97563e43bd58..1f3d6ef3b380 100644 --- a/tests/data/test2708 +++ b/tests/data/test2708 @@ -3,6 +3,7 @@ WebSockets +libtest diff --git a/tests/data/test2709 b/tests/data/test2709 index e3a3bb49ee1d..bd62fb6b42d6 100644 --- a/tests/data/test2709 +++ b/tests/data/test2709 @@ -3,6 +3,7 @@ WebSockets +libtest diff --git a/tests/data/test2710 b/tests/data/test2710 index ba8b0124fdc6..f58f9e33ff37 100644 --- a/tests/data/test2710 +++ b/tests/data/test2710 @@ -3,6 +3,7 @@ WebSockets +libtest diff --git a/tests/data/test2711 b/tests/data/test2711 index d0bc298b0c95..fbff03221b9d 100644 --- a/tests/data/test2711 +++ b/tests/data/test2711 @@ -3,6 +3,7 @@ WebSockets +libtest diff --git a/tests/data/test2712 b/tests/data/test2712 index b19058d0494b..9e2e603b2348 100644 --- a/tests/data/test2712 +++ b/tests/data/test2712 @@ -3,6 +3,7 @@ WebSockets +libtest diff --git a/tests/data/test2713 b/tests/data/test2713 index 6c50bc72fe06..2ea63895300d 100644 --- a/tests/data/test2713 +++ b/tests/data/test2713 @@ -3,6 +3,7 @@ WebSockets +libtest diff --git a/tests/data/test2714 b/tests/data/test2714 index 654004966f31..ef543ad2a20c 100644 --- a/tests/data/test2714 +++ b/tests/data/test2714 @@ -3,6 +3,7 @@ WebSockets +libtest diff --git a/tests/data/test2715 b/tests/data/test2715 index 4cf4febb3532..5d8fcd9c2494 100644 --- a/tests/data/test2715 +++ b/tests/data/test2715 @@ -3,6 +3,7 @@ WebSockets +libtest diff --git a/tests/data/test2716 b/tests/data/test2716 index 989e0a1ec90e..49dd99eabf61 100644 --- a/tests/data/test2716 +++ b/tests/data/test2716 @@ -3,6 +3,7 @@ WebSockets +libtest diff --git a/tests/data/test2717 b/tests/data/test2717 index 876f5849bacb..2c457a018553 100644 --- a/tests/data/test2717 +++ b/tests/data/test2717 @@ -3,6 +3,7 @@ WebSockets +libtest diff --git a/tests/data/test2718 b/tests/data/test2718 index aa44e21a8e0c..39c324c819e9 100644 --- a/tests/data/test2718 +++ b/tests/data/test2718 @@ -3,6 +3,7 @@ WebSockets +libtest diff --git a/tests/data/test2719 b/tests/data/test2719 index bfdd49b10467..afc431deb92b 100644 --- a/tests/data/test2719 +++ b/tests/data/test2719 @@ -3,6 +3,7 @@ WebSockets +libtest diff --git a/tests/data/test2720 b/tests/data/test2720 index 3a6c30d06e25..38bab0285752 100644 --- a/tests/data/test2720 +++ b/tests/data/test2720 @@ -3,6 +3,7 @@ WebSockets +libtest diff --git a/tests/data/test2721 b/tests/data/test2721 index a3260bad02ee..8e1ff1b3eb71 100644 --- a/tests/data/test2721 +++ b/tests/data/test2721 @@ -3,6 +3,7 @@ WebSockets +libtest diff --git a/tests/data/test2722 b/tests/data/test2722 index 61dbee612c36..65607d0eb3b5 100644 --- a/tests/data/test2722 +++ b/tests/data/test2722 @@ -3,6 +3,7 @@ WebSockets +libtest diff --git a/tests/data/test2723 b/tests/data/test2723 index 6fbac2bc8837..156ef00e08d3 100644 --- a/tests/data/test2723 +++ b/tests/data/test2723 @@ -3,6 +3,7 @@ WebSockets +libtest diff --git a/tests/data/test2805 b/tests/data/test2805 index 2006e4054953..9f151500f2e1 100644 --- a/tests/data/test2805 +++ b/tests/data/test2805 @@ -6,6 +6,7 @@ FTP LIST wildcardmatch ftplistparser +libtest diff --git a/tests/data/test2924 b/tests/data/test2924 index f56e35a835ec..7a852360f1f3 100644 --- a/tests/data/test2924 +++ b/tests/data/test2924 @@ -4,6 +4,7 @@ CURLOPT_HTTP_TRANSFER_DECODING chunked Transfer-Encoding +libtest diff --git a/tests/data/test3010 b/tests/data/test3010 index f56ca1496c40..770ca7d49204 100644 --- a/tests/data/test3010 +++ b/tests/data/test3010 @@ -5,6 +5,7 @@ HTTP HTTP Basic RETRY-AFTER +libtest diff --git a/tests/data/test3025 b/tests/data/test3025 index 6b44017d626b..37c07562825a 100644 --- a/tests/data/test3025 +++ b/tests/data/test3025 @@ -4,6 +4,7 @@ HTTP HTTP GET +libtest diff --git a/tests/data/test3026 b/tests/data/test3026 index d9a636f22380..6987634a492f 100644 --- a/tests/data/test3026 +++ b/tests/data/test3026 @@ -5,6 +5,7 @@ curl_global_init thread-safe slow +libtest diff --git a/tests/data/test3027 b/tests/data/test3027 index bc6b94647002..deb9e9c1d224 100644 --- a/tests/data/test3027 +++ b/tests/data/test3027 @@ -5,6 +5,7 @@ FTP CURLOPT_FILETIME MDTM fail +libtest diff --git a/tests/data/test3034 b/tests/data/test3034 index 5a30a9b55942..cfd1c5f447c6 100644 --- a/tests/data/test3034 +++ b/tests/data/test3034 @@ -5,6 +5,7 @@ CURLOPT_READFUNCTION curl_easy_reset rewind +libtest diff --git a/tests/data/test3100 b/tests/data/test3100 index 9afd58730690..4aaaace52bf2 100644 --- a/tests/data/test3100 +++ b/tests/data/test3100 @@ -4,6 +4,7 @@ RTSP RTSP Basic auth +libtest diff --git a/tests/data/test3101 b/tests/data/test3101 index bb385fe61a67..91ee83219d93 100644 --- a/tests/data/test3101 +++ b/tests/data/test3101 @@ -4,6 +4,7 @@ HTTP HTTP Basic auth +libtest diff --git a/tests/data/test3102 b/tests/data/test3102 index c66098cfbb69..aa4cda8feb8c 100644 --- a/tests/data/test3102 +++ b/tests/data/test3102 @@ -4,6 +4,7 @@ HTTPS HTTP GET +libtest diff --git a/tests/data/test3103 b/tests/data/test3103 index 40179a6fb8fa..681f0215b950 100644 --- a/tests/data/test3103 +++ b/tests/data/test3103 @@ -3,6 +3,7 @@ cookies +libtest diff --git a/tests/data/test3104 b/tests/data/test3104 index 42271d54bd4c..0ac708af87df 100644 --- a/tests/data/test3104 +++ b/tests/data/test3104 @@ -3,6 +3,7 @@ cookies +libtest diff --git a/tests/data/test3105 b/tests/data/test3105 index a85a89b59778..5198e04a9c1c 100644 --- a/tests/data/test3105 +++ b/tests/data/test3105 @@ -3,6 +3,7 @@ curl_multi_remove_handle +libtest diff --git a/tests/data/test3107 b/tests/data/test3107 index 3ff68b5d5a78..4b0e75900a87 100644 --- a/tests/data/test3107 +++ b/tests/data/test3107 @@ -6,6 +6,7 @@ HTTP HTTP GET multi curl_multi_remove_handle +libtest diff --git a/tests/data/test3207 b/tests/data/test3207 index ba15b1a47975..9334527640ed 100644 --- a/tests/data/test3207 +++ b/tests/data/test3207 @@ -3,6 +3,7 @@ HTTPS +libtest diff --git a/tests/data/test3231 b/tests/data/test3231 index 37b2cf3e1ad0..08038902a964 100644 --- a/tests/data/test3231 +++ b/tests/data/test3231 @@ -5,6 +5,7 @@ HTTP CONNECT_ONLY CURLMOPT_MAX_HOST_CONNECTIONS +libtest diff --git a/tests/data/test3232 b/tests/data/test3232 index 488118db6f2c..31dcd20d1f89 100644 --- a/tests/data/test3232 +++ b/tests/data/test3232 @@ -5,6 +5,7 @@ HTTP CONNECT_ONLY CURLMOPT_MAX_TOTAL_CONNECTIONS +libtest diff --git a/tests/data/test3233 b/tests/data/test3233 index 747ccbd9d678..0bb32dc02f54 100644 --- a/tests/data/test3233 +++ b/tests/data/test3233 @@ -5,6 +5,7 @@ RTSP SESSION_ID followlocation +libtest diff --git a/tests/data/test3595 b/tests/data/test3595 index b2b1dd26c21d..f740e61002b0 100644 --- a/tests/data/test3595 +++ b/tests/data/test3595 @@ -3,6 +3,7 @@ mime +libtest diff --git a/tests/data/test3596 b/tests/data/test3596 index 2b55123fffba..a93722de2bf0 100644 --- a/tests/data/test3596 +++ b/tests/data/test3596 @@ -3,6 +3,7 @@ mime +libtest diff --git a/tests/data/test500 b/tests/data/test500 index d4fe0666f4e6..d1459f095529 100644 --- a/tests/data/test500 +++ b/tests/data/test500 @@ -4,6 +4,7 @@ HTTP HTTP GET +libtest # Server-side diff --git a/tests/data/test5000 b/tests/data/test5000 index 3f2a98280206..a35212711ad5 100644 --- a/tests/data/test5000 +++ b/tests/data/test5000 @@ -5,6 +5,7 @@ HTTP CURLOPT_HTTPSIG_ALGORITHM RFC9421 +libtest diff --git a/tests/data/test5004 b/tests/data/test5004 index 33822b6b6d65..d0dc60970142 100644 --- a/tests/data/test5004 +++ b/tests/data/test5004 @@ -7,6 +7,7 @@ CURLOPT_HTTPSIG_ALGORITHM RFC9421 ed25519 B.2.6 +libtest diff --git a/tests/data/test501 b/tests/data/test501 index 52e2023df2f9..cfa1fe379fee 100644 --- a/tests/data/test501 +++ b/tests/data/test501 @@ -3,6 +3,7 @@ missing URL +libtest # Server-side diff --git a/tests/data/test502 b/tests/data/test502 index 5e0c57660826..de993097b94f 100644 --- a/tests/data/test502 +++ b/tests/data/test502 @@ -3,6 +3,7 @@ FILE +libtest # Server-side diff --git a/tests/data/test503 b/tests/data/test503 index b09ac69ddde7..8c735edb4fe8 100644 --- a/tests/data/test503 +++ b/tests/data/test503 @@ -9,6 +9,7 @@ HTTP proxy HTTP proxy Basic auth proxytunnel multi +libtest diff --git a/tests/data/test504 b/tests/data/test504 index c4670ee1b174..d70a576658e1 100644 --- a/tests/data/test504 +++ b/tests/data/test504 @@ -7,6 +7,7 @@ HTTP GET HTTP proxy multi connect to non-listen +libtest diff --git a/tests/data/test505 b/tests/data/test505 index be6be1b7f1ae..e99ed9f021e7 100644 --- a/tests/data/test505 +++ b/tests/data/test505 @@ -4,6 +4,7 @@ FTP STOR +libtest diff --git a/tests/data/test506 b/tests/data/test506 index ed8e4e315e75..addd21e5a8c9 100644 --- a/tests/data/test506 +++ b/tests/data/test506 @@ -6,6 +6,7 @@ HTTP HTTP GET cookies shared cookies +libtest diff --git a/tests/data/test507 b/tests/data/test507 index 5915ef70422b..da618f87292a 100644 --- a/tests/data/test507 +++ b/tests/data/test507 @@ -5,6 +5,7 @@ HTTP multi non-existing host +libtest diff --git a/tests/data/test508 b/tests/data/test508 index 11bf74a9b35f..cf48dfc0d036 100644 --- a/tests/data/test508 +++ b/tests/data/test508 @@ -5,6 +5,7 @@ HTTP POST POST callback +libtest # Server-side diff --git a/tests/data/test509 b/tests/data/test509 index a73fbaf5ca8b..dc2032624f0f 100644 --- a/tests/data/test509 +++ b/tests/data/test509 @@ -3,6 +3,7 @@ memory callbacks +libtest # Server-side diff --git a/tests/data/test510 b/tests/data/test510 index a865c577056a..f8c04388acad 100644 --- a/tests/data/test510 +++ b/tests/data/test510 @@ -5,6 +5,7 @@ HTTP HTTP POST chunked Transfer-Encoding +libtest diff --git a/tests/data/test511 b/tests/data/test511 index ea7deec2ad7a..9687778a499b 100644 --- a/tests/data/test511 +++ b/tests/data/test511 @@ -3,6 +3,7 @@ FTP +libtest diff --git a/tests/data/test512 b/tests/data/test512 index daa336273110..6947604eca47 100644 --- a/tests/data/test512 +++ b/tests/data/test512 @@ -4,6 +4,7 @@ HTTP HTTP GET +libtest diff --git a/tests/data/test513 b/tests/data/test513 index 551047c94059..18f2fb361a95 100644 --- a/tests/data/test513 +++ b/tests/data/test513 @@ -4,6 +4,7 @@ HTTP HTTP POST +libtest diff --git a/tests/data/test514 b/tests/data/test514 index fadbd9d50477..cf4c8db34dc6 100644 --- a/tests/data/test514 +++ b/tests/data/test514 @@ -4,6 +4,7 @@ HTTP HTTP HEAD +libtest # Server-side diff --git a/tests/data/test515 b/tests/data/test515 index 690f5d54cb35..261503720b0f 100644 --- a/tests/data/test515 +++ b/tests/data/test515 @@ -4,6 +4,7 @@ HTTP HTTP POST +libtest # Server-side diff --git a/tests/data/test516 b/tests/data/test516 index ae087387b4b6..d64d6915c275 100644 --- a/tests/data/test516 +++ b/tests/data/test516 @@ -5,6 +5,7 @@ HTTP HTTP POST FORM +libtest diff --git a/tests/data/test517 b/tests/data/test517 index 6af03b7b6204..c90659e5eedb 100644 --- a/tests/data/test517 +++ b/tests/data/test517 @@ -3,7 +3,7 @@ getdate -unittest +libtest # Server-side diff --git a/tests/data/test518 b/tests/data/test518 index 54aa63122197..946ee01575f2 100644 --- a/tests/data/test518 +++ b/tests/data/test518 @@ -4,6 +4,7 @@ HTTP HTTP GET +libtest diff --git a/tests/data/test519 b/tests/data/test519 index 53ad2a095c61..36a803bf6535 100644 --- a/tests/data/test519 +++ b/tests/data/test519 @@ -4,6 +4,7 @@ HTTP HTTP GET +libtest diff --git a/tests/data/test520 b/tests/data/test520 index 3c43d8ccc2b4..d0d34e8056bf 100644 --- a/tests/data/test520 +++ b/tests/data/test520 @@ -3,6 +3,7 @@ FTP +libtest diff --git a/tests/data/test521 b/tests/data/test521 index 84e1a3646d34..fef287c84563 100644 --- a/tests/data/test521 +++ b/tests/data/test521 @@ -5,6 +5,7 @@ FTP PASV CURLOPT_PORT +libtest # Server-side diff --git a/tests/data/test522 b/tests/data/test522 index ef76f5480f01..67002b168524 100644 --- a/tests/data/test522 +++ b/tests/data/test522 @@ -5,6 +5,7 @@ HTTP HTTP GET CURLOPT_PORT +libtest diff --git a/tests/data/test523 b/tests/data/test523 index 5c0db2c3e2ae..928e3afc4d1d 100644 --- a/tests/data/test523 +++ b/tests/data/test523 @@ -7,6 +7,7 @@ HTTP GET HTTP proxy CURLOPT_PORT CURLOPT_PROXY +libtest diff --git a/tests/data/test524 b/tests/data/test524 index 37b33cb835f2..6b946dfba277 100644 --- a/tests/data/test524 +++ b/tests/data/test524 @@ -4,6 +4,7 @@ FTP UPLOAD +libtest diff --git a/tests/data/test525 b/tests/data/test525 index 11bc85c1571b..7c6f152ea902 100644 --- a/tests/data/test525 +++ b/tests/data/test525 @@ -6,6 +6,7 @@ FTP PORT STOR multi +libtest diff --git a/tests/data/test526 b/tests/data/test526 index a55a1f5eaeb3..daaf2e4ff728 100644 --- a/tests/data/test526 +++ b/tests/data/test526 @@ -6,6 +6,7 @@ FTP PASV RETR multi +libtest diff --git a/tests/data/test527 b/tests/data/test527 index 735bd20e8528..292e45af015c 100644 --- a/tests/data/test527 +++ b/tests/data/test527 @@ -6,6 +6,7 @@ FTP PASV RETR multi +libtest diff --git a/tests/data/test528 b/tests/data/test528 index 54f170727649..3e9f39b07637 100644 --- a/tests/data/test528 +++ b/tests/data/test528 @@ -5,6 +5,7 @@ HTTP HTTP GET multi +libtest diff --git a/tests/data/test529 b/tests/data/test529 index 52495ce63c6e..3f6cd30f701c 100644 --- a/tests/data/test529 +++ b/tests/data/test529 @@ -6,6 +6,7 @@ FTP PORT STOR multi +libtest diff --git a/tests/data/test530 b/tests/data/test530 index 0875b5e5f81e..feb96b9a27e8 100644 --- a/tests/data/test530 +++ b/tests/data/test530 @@ -4,6 +4,7 @@ multi HTTP +libtest diff --git a/tests/data/test531 b/tests/data/test531 index a43d4d787ee8..84989087c1b6 100644 --- a/tests/data/test531 +++ b/tests/data/test531 @@ -6,6 +6,7 @@ FTP PORT STOR multi +libtest diff --git a/tests/data/test532 b/tests/data/test532 index aed9c3dd773b..ac95b9dfb859 100644 --- a/tests/data/test532 +++ b/tests/data/test532 @@ -6,6 +6,7 @@ FTP PASV RETR multi +libtest diff --git a/tests/data/test533 b/tests/data/test533 index e0ca3b014260..1318d4583bd7 100644 --- a/tests/data/test533 +++ b/tests/data/test533 @@ -6,6 +6,7 @@ FTP PASV RETR multi +libtest diff --git a/tests/data/test534 b/tests/data/test534 index 5ef1f68abd03..a235b41c85be 100644 --- a/tests/data/test534 +++ b/tests/data/test534 @@ -7,6 +7,7 @@ PASV RETR multi non-existing host +libtest diff --git a/tests/data/test535 b/tests/data/test535 index 49802e21ca90..f493662e93fc 100644 --- a/tests/data/test535 +++ b/tests/data/test535 @@ -5,6 +5,7 @@ HTTP HTTP GET multi +libtest diff --git a/tests/data/test536 b/tests/data/test536 index e0bcc906f7de..8d7f0510cf9e 100644 --- a/tests/data/test536 +++ b/tests/data/test536 @@ -8,6 +8,7 @@ HTTP proxy CURLOPT_PROXY CURLOPT_NOBODY CURLINFO_USED_PROXY +libtest diff --git a/tests/data/test537 b/tests/data/test537 index 177636b3e069..e365afd69a84 100644 --- a/tests/data/test537 +++ b/tests/data/test537 @@ -4,6 +4,7 @@ HTTP HTTP GET +libtest diff --git a/tests/data/test538 b/tests/data/test538 index 69e6fa931c35..39fe02bbc5ae 100644 --- a/tests/data/test538 +++ b/tests/data/test538 @@ -4,6 +4,7 @@ FTP multi +libtest diff --git a/tests/data/test539 b/tests/data/test539 index 2a487c652a62..144a127380cc 100644 --- a/tests/data/test539 +++ b/tests/data/test539 @@ -3,6 +3,7 @@ FTP +libtest diff --git a/tests/data/test540 b/tests/data/test540 index 0999f5e96993..8842d9ff6f96 100644 --- a/tests/data/test540 +++ b/tests/data/test540 @@ -7,6 +7,7 @@ HTTP GET HTTP proxy HTTP proxy Digest auth multi +libtest diff --git a/tests/data/test541 b/tests/data/test541 index 67039a3e8173..324604206072 100644 --- a/tests/data/test541 +++ b/tests/data/test541 @@ -4,6 +4,7 @@ FTP STOR +libtest diff --git a/tests/data/test542 b/tests/data/test542 index 29aa562d6869..494f239b3915 100644 --- a/tests/data/test542 +++ b/tests/data/test542 @@ -5,6 +5,7 @@ FTP PASV RETR +libtest # Server-side diff --git a/tests/data/test543 b/tests/data/test543 index ec6677bf0b87..d40475238542 100644 --- a/tests/data/test543 +++ b/tests/data/test543 @@ -3,6 +3,7 @@ curl_easy_escape +libtest # Server-side diff --git a/tests/data/test544 b/tests/data/test544 index f71cd095e784..fd798746c356 100644 --- a/tests/data/test544 +++ b/tests/data/test544 @@ -4,6 +4,7 @@ HTTP HTTP POST +libtest diff --git a/tests/data/test545 b/tests/data/test545 index 0cffd7b20be5..d25465048b0e 100644 --- a/tests/data/test545 +++ b/tests/data/test545 @@ -4,6 +4,7 @@ HTTP HTTP POST +libtest diff --git a/tests/data/test546 b/tests/data/test546 index 97459e9e3b52..141752d02caf 100644 --- a/tests/data/test546 +++ b/tests/data/test546 @@ -6,6 +6,7 @@ FTP PASV RETR multi +libtest # Server-side diff --git a/tests/data/test547 b/tests/data/test547 index 723d687f1048..143f98d7cd2b 100644 --- a/tests/data/test547 +++ b/tests/data/test547 @@ -8,6 +8,7 @@ POST callback HTTP proxy HTTP proxy NTLM auth NTLM +libtest # Server-side diff --git a/tests/data/test548 b/tests/data/test548 index 2a60640fc224..1f12ff1d5571 100644 --- a/tests/data/test548 +++ b/tests/data/test548 @@ -7,6 +7,7 @@ HTTP POST HTTP proxy HTTP proxy NTLM auth NTLM +libtest diff --git a/tests/data/test549 b/tests/data/test549 index c20612fc274a..15955bf3ba3f 100644 --- a/tests/data/test549 +++ b/tests/data/test549 @@ -6,6 +6,7 @@ FTP CURLOPT_PROXY_TRANSFER_MODE CURLOPT_PROXY HTTP proxy +libtest diff --git a/tests/data/test550 b/tests/data/test550 index 7ef61f89a431..804f83528430 100644 --- a/tests/data/test550 +++ b/tests/data/test550 @@ -6,6 +6,7 @@ FTP HTTP proxy CURLOPT_PROXY_TRANSFER_MODE CURLOPT_PROXY +libtest diff --git a/tests/data/test551 b/tests/data/test551 index 410214523026..42f524d50128 100644 --- a/tests/data/test551 +++ b/tests/data/test551 @@ -8,6 +8,7 @@ HTTP POST POST callback HTTP proxy HTTP proxy Digest auth +libtest diff --git a/tests/data/test552 b/tests/data/test552 index 621f378b1ec5..5bcda6a50bfd 100644 --- a/tests/data/test552 +++ b/tests/data/test552 @@ -11,6 +11,7 @@ HTTP POST POST callback HTTP proxy HTTP proxy Digest auth +libtest # Server-side diff --git a/tests/data/test553 b/tests/data/test553 index 0a1ce9e414cd..85c1623201c0 100644 --- a/tests/data/test553 +++ b/tests/data/test553 @@ -6,6 +6,7 @@ HTTP HTTP POST POST callback huge request header +libtest # Server-side diff --git a/tests/data/test554 b/tests/data/test554 index 04be5b582e98..ebd6a770df57 100644 --- a/tests/data/test554 +++ b/tests/data/test554 @@ -5,6 +5,7 @@ HTTP HTTP POST FORM +libtest diff --git a/tests/data/test555 b/tests/data/test555 index bb2ff515e8a9..d5aa924338ff 100644 --- a/tests/data/test555 +++ b/tests/data/test555 @@ -12,6 +12,7 @@ HTTP proxy HTTP proxy NTLM auth multi NTLM +libtest diff --git a/tests/data/test556 b/tests/data/test556 index 7d39cc1737c6..9352fe7ab18b 100644 --- a/tests/data/test556 +++ b/tests/data/test556 @@ -4,6 +4,7 @@ HTTP HTTP GET +libtest diff --git a/tests/data/test557 b/tests/data/test557 index df859e97c7c6..9a23b58f6623 100644 --- a/tests/data/test557 +++ b/tests/data/test557 @@ -3,7 +3,7 @@ printf -unittest +libtest # Server-side diff --git a/tests/data/test558 b/tests/data/test558 index 66e8769a019a..29249ab4e904 100644 --- a/tests/data/test558 +++ b/tests/data/test558 @@ -3,6 +3,7 @@ TrackMemory +libtest diff --git a/tests/data/test559 b/tests/data/test559 index 35f888ad469a..297aa6309d77 100644 --- a/tests/data/test559 +++ b/tests/data/test559 @@ -4,6 +4,7 @@ CURLOPT_BUFFERSIZE HTTP +libtest diff --git a/tests/data/test560 b/tests/data/test560 index 9aa36a479536..7c406f2977f6 100644 --- a/tests/data/test560 +++ b/tests/data/test560 @@ -5,6 +5,7 @@ HTTPS HTTP GET multi +libtest diff --git a/tests/data/test561 b/tests/data/test561 index bd752397f3dc..20ee5e7cd7ea 100644 --- a/tests/data/test561 +++ b/tests/data/test561 @@ -7,6 +7,7 @@ HTTP proxy CURLOPT_PROXY_TRANSFER_MODE CURLOPT_PROXY type= +libtest diff --git a/tests/data/test562 b/tests/data/test562 index 6e0778b66103..01733ec56f3e 100644 --- a/tests/data/test562 +++ b/tests/data/test562 @@ -5,6 +5,7 @@ FTP PASV RETR +libtest # Server-side diff --git a/tests/data/test563 b/tests/data/test563 index c3f09884a3fa..987a8eab6db4 100644 --- a/tests/data/test563 +++ b/tests/data/test563 @@ -5,6 +5,7 @@ FTP CURLOPT_PORT HTTP proxy +libtest diff --git a/tests/data/test564 b/tests/data/test564 index dba2a7fb0365..9a4cf74ae057 100644 --- a/tests/data/test564 +++ b/tests/data/test564 @@ -9,6 +9,7 @@ PASV RETR multi SOCKS4 +libtest # Server-side diff --git a/tests/data/test565 b/tests/data/test565 index 6975239b77de..06e8e6509e1b 100644 --- a/tests/data/test565 +++ b/tests/data/test565 @@ -6,6 +6,7 @@ HTTP HTTP POST HTTP Digest auth chunked Transfer-Encoding +libtest # Server-side diff --git a/tests/data/test566 b/tests/data/test566 index 993ba79635a5..8457b9ff2d91 100644 --- a/tests/data/test566 +++ b/tests/data/test566 @@ -4,6 +4,7 @@ HTTP HTTP GET +libtest # Server-side diff --git a/tests/data/test567 b/tests/data/test567 index 756e8d438ea4..1159c222d832 100644 --- a/tests/data/test567 +++ b/tests/data/test567 @@ -4,6 +4,7 @@ RTSP OPTIONS +libtest diff --git a/tests/data/test568 b/tests/data/test568 index 07b610dcc6d0..8cce6096212b 100644 --- a/tests/data/test568 +++ b/tests/data/test568 @@ -4,6 +4,7 @@ RTSP ANNOUNCE +libtest diff --git a/tests/data/test569 b/tests/data/test569 index 1e6ec7c00f66..a9f0a971cabe 100644 --- a/tests/data/test569 +++ b/tests/data/test569 @@ -4,6 +4,7 @@ RTSP SESSION_ID +libtest diff --git a/tests/data/test570 b/tests/data/test570 index 10739b071af2..6009465c1ae1 100644 --- a/tests/data/test570 +++ b/tests/data/test570 @@ -4,6 +4,7 @@ RTSP Mismatch checking +libtest diff --git a/tests/data/test571 b/tests/data/test571 index 258ee6ae68d9..675ae5c83b38 100644 --- a/tests/data/test571 +++ b/tests/data/test571 @@ -8,6 +8,7 @@ RTSP RTP +libtest diff --git a/tests/data/test572 b/tests/data/test572 index 7f887f6b98d6..d16ecd42beb2 100644 --- a/tests/data/test572 +++ b/tests/data/test572 @@ -4,6 +4,7 @@ RTSP GET_PARAMETER +libtest diff --git a/tests/data/test573 b/tests/data/test573 index 24c674dd9340..b128bb4253c7 100644 --- a/tests/data/test573 +++ b/tests/data/test573 @@ -5,6 +5,7 @@ HTTP multi flaky +libtest # Server-side diff --git a/tests/data/test574 b/tests/data/test574 index 38dfb7ab800d..63674404ec2a 100644 --- a/tests/data/test574 +++ b/tests/data/test574 @@ -7,6 +7,7 @@ LIST RETR wildcardmatch ftplistparser +libtest diff --git a/tests/data/test575 b/tests/data/test575 index 2f4cf3c6bbbb..e4b99184b651 100644 --- a/tests/data/test575 +++ b/tests/data/test575 @@ -7,6 +7,7 @@ RETR multi wildcardmatch ftplistparser +libtest diff --git a/tests/data/test576 b/tests/data/test576 index 7ea01077656c..5437008d13a9 100644 --- a/tests/data/test576 +++ b/tests/data/test576 @@ -5,6 +5,7 @@ FTP wildcardmatch ftplistparser +libtest diff --git a/tests/data/test577 b/tests/data/test577 index 5016e1b0d2c7..bc4937d17e52 100644 --- a/tests/data/test577 +++ b/tests/data/test577 @@ -4,6 +4,7 @@ RTSP OPTIONS +libtest diff --git a/tests/data/test578 b/tests/data/test578 index f3768cab1253..7935bfffa6c9 100644 --- a/tests/data/test578 +++ b/tests/data/test578 @@ -4,6 +4,7 @@ HTTP HTTP POST +libtest # Server-side diff --git a/tests/data/test579 b/tests/data/test579 index 127223950064..7b2164b7a6fe 100644 --- a/tests/data/test579 +++ b/tests/data/test579 @@ -5,6 +5,7 @@ HTTP HTTP POST HTTP Digest auth +libtest # Server-side diff --git a/tests/data/test580 b/tests/data/test580 index da043e8125ce..cd4dd4a3ff44 100644 --- a/tests/data/test580 +++ b/tests/data/test580 @@ -6,6 +6,7 @@ HTTP HTTP GET multi Duplicate-header +libtest diff --git a/tests/data/test581 b/tests/data/test581 index 818ee7149e2e..0d92cf6579e7 100644 --- a/tests/data/test581 +++ b/tests/data/test581 @@ -6,6 +6,7 @@ HTTP HTTP GET multi Duplicate-header +libtest diff --git a/tests/data/test582 b/tests/data/test582 index ca533b8c4b6f..758bc37485fc 100644 --- a/tests/data/test582 +++ b/tests/data/test582 @@ -4,6 +4,7 @@ SFTP multi +libtest diff --git a/tests/data/test583 b/tests/data/test583 index 8428d31395c9..97cf60a9f8c3 100644 --- a/tests/data/test583 +++ b/tests/data/test583 @@ -4,6 +4,7 @@ SFTP multi +libtest diff --git a/tests/data/test584 b/tests/data/test584 index 563481edb522..661a58cc88ab 100644 --- a/tests/data/test584 +++ b/tests/data/test584 @@ -5,6 +5,7 @@ HTTP HTTP POST HTTP MIME +libtest diff --git a/tests/data/test585 b/tests/data/test585 index bd5c569a8afd..600f756a3060 100644 --- a/tests/data/test585 +++ b/tests/data/test585 @@ -7,6 +7,7 @@ HTTP GET multi CURLOPT_OPENSOCKETFUNCTION CURLOPT_CLOSESOCKETFUNCTION +libtest diff --git a/tests/data/test586 b/tests/data/test586 index d0da079c98cb..840f0ddb870e 100644 --- a/tests/data/test586 +++ b/tests/data/test586 @@ -5,6 +5,7 @@ FTP PASV CURLOPT_PORT +libtest # Server-side diff --git a/tests/data/test587 b/tests/data/test587 index 74dbfe3b8f43..a5c69b333d5f 100644 --- a/tests/data/test587 +++ b/tests/data/test587 @@ -6,6 +6,7 @@ HTTP HTTP POST FORM flaky +libtest diff --git a/tests/data/test588 b/tests/data/test588 index da0bceba7722..6286a4dd2cdf 100644 --- a/tests/data/test588 +++ b/tests/data/test588 @@ -10,6 +10,7 @@ PORT STOR multi EPRT refused +libtest diff --git a/tests/data/test589 b/tests/data/test589 index 949e5b6f719f..b22a92c395ea 100644 --- a/tests/data/test589 +++ b/tests/data/test589 @@ -5,6 +5,7 @@ HTTP HTTP POST HTTP MIME +libtest diff --git a/tests/data/test590 b/tests/data/test590 index 8d8400845e44..15266f25823e 100644 --- a/tests/data/test590 +++ b/tests/data/test590 @@ -8,6 +8,7 @@ POST callback HTTP proxy HTTP proxy NTLM auth NTLM +libtest # Server-side diff --git a/tests/data/test591 b/tests/data/test591 index fbcd1b942632..72363d894017 100644 --- a/tests/data/test591 +++ b/tests/data/test591 @@ -11,6 +11,7 @@ STOR multi EPRT refused NODATACONN425 +libtest diff --git a/tests/data/test592 b/tests/data/test592 index 5ef624ae1bb7..77992ae45542 100644 --- a/tests/data/test592 +++ b/tests/data/test592 @@ -11,6 +11,7 @@ STOR multi EPRT refused NODATACONN421 +libtest diff --git a/tests/data/test593 b/tests/data/test593 index cb45d7b8d549..e75de1d6edf9 100644 --- a/tests/data/test593 +++ b/tests/data/test593 @@ -11,6 +11,7 @@ STOR multi EPRT refused NODATACONN150 +libtest diff --git a/tests/data/test594 b/tests/data/test594 index aaa007cbde8a..e59168dc828c 100644 --- a/tests/data/test594 +++ b/tests/data/test594 @@ -12,6 +12,7 @@ multi EPRT refused NODATACONN timeout +libtest diff --git a/tests/data/test595 b/tests/data/test595 index d29be62c76c5..775d6d75f9d8 100644 --- a/tests/data/test595 +++ b/tests/data/test595 @@ -4,6 +4,7 @@ FTP +libtest # Server-side diff --git a/tests/data/test596 b/tests/data/test596 index 4696aa900f56..e709716b48a3 100644 --- a/tests/data/test596 +++ b/tests/data/test596 @@ -4,6 +4,7 @@ FTP +libtest # Server-side diff --git a/tests/data/test597 b/tests/data/test597 index d65afa72214b..41cc23070882 100644 --- a/tests/data/test597 +++ b/tests/data/test597 @@ -4,6 +4,7 @@ FTP CONNECT_ONLY +libtest diff --git a/tests/data/test598 b/tests/data/test598 index 73c5b82ece48..1bd579c5da5c 100644 --- a/tests/data/test598 +++ b/tests/data/test598 @@ -8,6 +8,7 @@ HTTP set cookie cookies CURLOPT_REFERER curl_easy_reset +libtest # Server-side diff --git a/tests/data/test599 b/tests/data/test599 index a8c883dc2d24..0db3e330f3f9 100644 --- a/tests/data/test599 +++ b/tests/data/test599 @@ -5,6 +5,7 @@ HTTP HTTP POST chunked Transfer-Encoding +libtest # Server-side diff --git a/tests/data/test643 b/tests/data/test643 index ad3d612e4d8f..12cc213a557b 100644 --- a/tests/data/test643 +++ b/tests/data/test643 @@ -5,6 +5,7 @@ HTTP HTTP POST HTTP MIME POST +libtest diff --git a/tests/data/test645 b/tests/data/test645 index 7e48e20a877c..63f4d5218566 100644 --- a/tests/data/test645 +++ b/tests/data/test645 @@ -5,6 +5,7 @@ HTTP HTTP POST HTTP MIME POST +libtest diff --git a/tests/data/test650 b/tests/data/test650 index 8e8fb28809a2..32ef3135c31f 100644 --- a/tests/data/test650 +++ b/tests/data/test650 @@ -5,6 +5,7 @@ HTTP HTTP POST FORM +libtest diff --git a/tests/data/test651 b/tests/data/test651 index 49cf1aab9331..8a062872f5e0 100644 --- a/tests/data/test651 +++ b/tests/data/test651 @@ -5,6 +5,7 @@ HTTP HTTP POST FORM +libtest diff --git a/tests/data/test652 b/tests/data/test652 index 5315ab4baaa7..967941a56982 100644 --- a/tests/data/test652 +++ b/tests/data/test652 @@ -4,6 +4,7 @@ SMTP MIME +libtest diff --git a/tests/data/test653 b/tests/data/test653 index 3674f4e07ba0..7287da9fc421 100644 --- a/tests/data/test653 +++ b/tests/data/test653 @@ -5,6 +5,7 @@ HTTP HTTP POST MIME +libtest diff --git a/tests/data/test654 b/tests/data/test654 index 34e052836cbe..84a4d9e40681 100644 --- a/tests/data/test654 +++ b/tests/data/test654 @@ -5,6 +5,7 @@ HTTP HTTP POST HTTP MIME POST +libtest diff --git a/tests/data/test655 b/tests/data/test655 index ce2a1726f76f..5273bac81a68 100644 --- a/tests/data/test655 +++ b/tests/data/test655 @@ -3,6 +3,7 @@ HTTP +libtest diff --git a/tests/data/test657 b/tests/data/test657 index 1bd24aae6538..ec9e21514ccb 100644 --- a/tests/data/test657 +++ b/tests/data/test657 @@ -4,6 +4,7 @@ RTSP OPTIONS +libtest diff --git a/tests/data/test658 b/tests/data/test658 index 2ec0e0661e40..441169bd81fa 100644 --- a/tests/data/test658 +++ b/tests/data/test658 @@ -5,6 +5,7 @@ HTTP HTTP GET CURLOPT_CURLU +libtest diff --git a/tests/data/test659 b/tests/data/test659 index e16f2ad75372..c767738eca4f 100644 --- a/tests/data/test659 +++ b/tests/data/test659 @@ -6,6 +6,7 @@ HTTP HTTP GET CURLOPT_CURLU proxy +libtest diff --git a/tests/data/test660 b/tests/data/test660 index 62ca998ec3bc..45685f7649b3 100644 --- a/tests/data/test660 +++ b/tests/data/test660 @@ -4,6 +4,7 @@ IMAP CONNECT_ONLY +libtest diff --git a/tests/data/test661 b/tests/data/test661 index c2e7e5cbc107..96d85a7272b6 100644 --- a/tests/data/test661 +++ b/tests/data/test661 @@ -3,6 +3,7 @@ FTP +libtest diff --git a/tests/data/test666 b/tests/data/test666 index 2ed6ea60c6ca..d15cdeccc8fa 100644 --- a/tests/data/test666 +++ b/tests/data/test666 @@ -6,6 +6,7 @@ HTTP HTTP POST HTTP MIME POST MIME +libtest diff --git a/tests/data/test667 b/tests/data/test667 index d1a4d41b9c58..2085ed454071 100644 --- a/tests/data/test667 +++ b/tests/data/test667 @@ -5,6 +5,7 @@ HTTP HTTP POST HTTP MIME POST +libtest diff --git a/tests/data/test668 b/tests/data/test668 index 288f8c134337..01c186875424 100644 --- a/tests/data/test668 +++ b/tests/data/test668 @@ -5,6 +5,7 @@ HTTP HTTP POST HTTP MIME POST +libtest diff --git a/tests/data/test670 b/tests/data/test670 index 77262b1049cd..a5bd21699e79 100644 --- a/tests/data/test670 +++ b/tests/data/test670 @@ -5,6 +5,7 @@ HTTP HTTP POST MIME +libtest diff --git a/tests/data/test671 b/tests/data/test671 index 5d7e3ef6d010..af35b71b0a94 100644 --- a/tests/data/test671 +++ b/tests/data/test671 @@ -5,6 +5,7 @@ HTTP HTTP POST MIME +libtest diff --git a/tests/data/test672 b/tests/data/test672 index e89923e19e47..1e351374b752 100644 --- a/tests/data/test672 +++ b/tests/data/test672 @@ -5,6 +5,7 @@ HTTP HTTP POST FORM +libtest diff --git a/tests/data/test673 b/tests/data/test673 index ed15cea6d894..c5435d4bc7cd 100644 --- a/tests/data/test673 +++ b/tests/data/test673 @@ -5,6 +5,7 @@ HTTP HTTP POST FORM +libtest diff --git a/tests/data/test674 b/tests/data/test674 index 745d8eaa38ed..e322b55a15e6 100644 --- a/tests/data/test674 +++ b/tests/data/test674 @@ -6,6 +6,7 @@ HTTP HTTP GET CURLOPT_CURLU curl_easy_duphandle +libtest diff --git a/tests/data/test676 b/tests/data/test676 index 0da221e68a07..e61097aff2bf 100644 --- a/tests/data/test676 +++ b/tests/data/test676 @@ -8,6 +8,7 @@ HTTP set cookie cookies CURLOPT_REFERER curl_easy_reset +libtest # Server-side diff --git a/tests/data/test677 b/tests/data/test677 index ebff275c0253..942903277e62 100644 --- a/tests/data/test677 +++ b/tests/data/test677 @@ -4,6 +4,7 @@ IMAP CONNECT_ONLY +libtest diff --git a/tests/data/test678 b/tests/data/test678 index 8df67074404e..895ebcff528b 100644 --- a/tests/data/test678 +++ b/tests/data/test678 @@ -5,6 +5,7 @@ HTTPS HTTP GET PEM certificate +libtest diff --git a/tests/data/test689 b/tests/data/test689 index 13f514bd74e2..27f8e0469e05 100644 --- a/tests/data/test689 +++ b/tests/data/test689 @@ -4,6 +4,7 @@ RTSP OPTIONS +libtest diff --git a/tests/data/test694 b/tests/data/test694 index bfc4181eba70..3d05997e099f 100644 --- a/tests/data/test694 +++ b/tests/data/test694 @@ -8,6 +8,7 @@ POST callback HTTP proxy HTTP proxy NTLM auth NTLM +libtest # Server-side diff --git a/tests/data/test695 b/tests/data/test695 index df6574473199..ebfa6f9cdab2 100644 --- a/tests/data/test695 +++ b/tests/data/test695 @@ -3,6 +3,7 @@ MIME +libtest # Server-side diff --git a/tests/data/test696 b/tests/data/test696 index ce85a5f5b37d..4cbdf85757e7 100644 --- a/tests/data/test696 +++ b/tests/data/test696 @@ -4,6 +4,7 @@ HTTP HTTP GET +libtest diff --git a/tests/data/test751 b/tests/data/test751 index f642733c7f69..7e9a6165f4e5 100644 --- a/tests/data/test751 +++ b/tests/data/test751 @@ -3,6 +3,7 @@ MULTI +libtest diff --git a/tests/data/test753 b/tests/data/test753 index 868c7f2c4cf3..2a115d2f0b53 100644 --- a/tests/data/test753 +++ b/tests/data/test753 @@ -3,6 +3,7 @@ FTP +libtest diff --git a/tests/data/test757 b/tests/data/test757 index 80d7706d61f7..1aa710e29f42 100644 --- a/tests/data/test757 +++ b/tests/data/test757 @@ -3,6 +3,7 @@ MIME +libtest # Server-side diff --git a/tests/data/test758 b/tests/data/test758 index 08f024fecd61..f7a610803121 100644 --- a/tests/data/test758 +++ b/tests/data/test758 @@ -4,6 +4,7 @@ multi HTTPS +libtest diff --git a/tests/data/test766 b/tests/data/test766 index 6b08c7226f27..e3f3b6034ab4 100644 --- a/tests/data/test766 +++ b/tests/data/test766 @@ -6,6 +6,7 @@ FTP PORT CURLOPT_SOCKOPTFUNCTION timing-dependent +libtest From f2a1535712ff8622024d27b867480698752c254e Mon Sep 17 00:00:00 2001 From: Stefan Eissing Date: Tue, 22 Sep 2026 10:31:06 +0200 Subject: [PATCH 200/481] curl_easy_send/recv: check pointer arg Return `CURLE_BAD_FUNCTION_ARGUMENT` when a `NULL` has been passed as arg `n`. Reported-by: Joshua Rogers Closes #23035 --- lib/easy.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/lib/easy.c b/lib/easy.c index 721f3a0c7a27..115a5f28767d 100644 --- a/lib/easy.c +++ b/lib/easy.c @@ -1304,8 +1304,13 @@ CURLcode curl_easy_recv(CURL *curl, void *buffer, size_t buflen, size_t *n) CURLcode result; if(CURL_EAPI_ENTER(&guard, curl, easy_recv, &result)) { + if(!n) { + result = CURLE_BAD_FUNCTION_ARGUMENT; + goto out; + } result = Curl_easy_recv(curl, buffer, buflen, n); } +out: CURL_EAPI_LEAVE(&guard); return result; } @@ -1374,9 +1379,14 @@ CURLcode curl_easy_send(CURL *curl, const void *buffer, size_t buflen, struct Curl_easy *data = curl; size_t written = 0; + if(!n) { + result = CURLE_BAD_FUNCTION_ARGUMENT; + goto out; + } result = Curl_senddata(data, buffer, buflen, &written); *n = written; } +out: CURL_EAPI_LEAVE(&guard); return result; } From 02f5a598bb08b0ce10819049674520207fe560f4 Mon Sep 17 00:00:00 2001 From: Stefan Eissing Date: Tue, 22 Sep 2026 11:30:23 +0200 Subject: [PATCH 201/481] cf-ngtcp2-proxy: fix rx flow control Fix H3-PROXY receive flow control to properly account for the data buffered and update windows on buffer consumption. Closes #23038 --- lib/vquic/cf-ngtcp2-cmn.c | 1 - lib/vquic/cf-ngtcp2-cmn.h | 1 - lib/vquic/cf-ngtcp2-proxy.c | 64 +++++++++++++--------------------- tests/http/test_60_h3_proxy.py | 3 +- 4 files changed, 27 insertions(+), 42 deletions(-) diff --git a/lib/vquic/cf-ngtcp2-cmn.c b/lib/vquic/cf-ngtcp2-cmn.c index 38acff006569..0110a03f7013 100644 --- a/lib/vquic/cf-ngtcp2-cmn.c +++ b/lib/vquic/cf-ngtcp2-cmn.c @@ -1897,7 +1897,6 @@ CURLcode Curl_cf_ngtcp2_h3_stream_setup(struct Curl_cfilter *cf, Curl_bufq_initp(&stream->sendbuf, &ctx->stream_bufcp, H3_STREAM_SEND_CHUNKS, BUFQ_OPT_NONE); stream->tx_in_flight_size = 0; - stream->window_size_max = H3_STREAM_WINDOW_SIZE_INITIAL; Curl_h1_req_parse_init(&stream->h1, H1_PARSE_DEFAULT_MAX_LINE_LEN); if(Curl_u32_ptrset_set(&ctx->streams, data->mid, stream)) { diff --git a/lib/vquic/cf-ngtcp2-cmn.h b/lib/vquic/cf-ngtcp2-cmn.h index 0c8b26a255a3..9f1bd7280c94 100644 --- a/lib/vquic/cf-ngtcp2-cmn.h +++ b/lib/vquic/cf-ngtcp2-cmn.h @@ -172,7 +172,6 @@ struct h3_stream_ctx { curl_off_t rx_total; /* total number of bytes received */ uint64_t rx_offset; /* current receive offset */ uint64_t rx_offset_max; /* allowed receive offset */ - uint64_t window_size_max; /* max flow control window set for stream */ size_t tx_in_flight_size; /* sendbuf data "in flight" */ size_t tx_in_flight_ideal; /* ideal amount of un-acked send data */ int status_code; /* HTTP status code */ diff --git a/lib/vquic/cf-ngtcp2-proxy.c b/lib/vquic/cf-ngtcp2-proxy.c index b68f5c888aca..59e9228575bb 100644 --- a/lib/vquic/cf-ngtcp2-proxy.c +++ b/lib/vquic/cf-ngtcp2-proxy.c @@ -46,7 +46,9 @@ * each active transfer. We use HTTP/3 flow control and only ACK * when we take things out of the buffer. * Chunk size is large enough to take a full DATA frame */ -#define PROXY_H3_STREAM_RECV_CHUNKS ((512 * 1024) / H3_STREAM_CHUNK_SIZE) +#define PROXY_H3_TUNNEL_RX_WIN (512 * 1024) +#define PROXY_H3_TUNNEL_RECV_CHUNKS \ + (PROXY_H3_TUNNEL_RX_WIN / H3_STREAM_CHUNK_SIZE) typedef enum { H3_TUNNEL_INIT, /* init/default/no tunnel state */ @@ -75,7 +77,7 @@ static CURLcode h3_tunnel_stream_init(struct h3_tunnel_stream *ts, ts->state = H3_TUNNEL_INIT; Curl_peer_link(&ts->dest, dest); Curl_bufq_init2(&ts->recvbuf, H3_STREAM_CHUNK_SIZE, - PROXY_H3_STREAM_RECV_CHUNKS, BUFQ_OPT_SOFT_LIMIT); + PROXY_H3_TUNNEL_RECV_CHUNKS, BUFQ_OPT_SOFT_LIMIT); ts->udp = udp; /* host:port with IPv6 support */ ts->authority = curl_maprintf("%s%s%s:%u", dest->ipv6 ? "[" : "", @@ -271,47 +273,31 @@ static void cf_h3_proxy_upd_rx_win(struct Curl_cfilter *cf, { struct cf_h3_proxy_ctx *pctx = cf->ctx; struct cf_ngtcp2_ctx *ctx = &pctx->ngtcp2_ctx; - uint64_t cur_win, wanted_win = H3_STREAM_WINDOW_SIZE_MAX; - - /* how much does rate limiting allow us to acknowledge? */ - if(Curl_rlimit_active(&data->progress.dl.rlimit)) { - int64_t avail; - - /* start rate limit updates only after first bytes arrived */ - if(!stream->rx_offset) - return; - - avail = Curl_rlimit_avail(&data->progress.dl.rlimit, NULL); - if(avail <= 0) { - /* nothing available, do not extend the rx offset */ - CURL_TRC_CF(data, cf, "[%" PRId64 "] dl rate limit exhausted (%" PRId64 - " tokens)", stream->id, avail); - return; - } - wanted_win = CURLMIN((uint64_t)avail, H3_STREAM_WINDOW_SIZE_MAX); - } + uint64_t cur_win, wanted_win = 0; + size_t rx_buffered; if(stream->rx_offset_max < stream->rx_offset) { DEBUGASSERT(0); return; } + + rx_buffered = Curl_bufq_len(&pctx->tunnel.recvbuf); + wanted_win = (rx_buffered < PROXY_H3_TUNNEL_RX_WIN) ? + (PROXY_H3_TUNNEL_RX_WIN - rx_buffered) : 0; cur_win = stream->rx_offset_max - stream->rx_offset; - if(cur_win < wanted_win) { - /* We have exhausted the credit we gave the QUIC peer for DATA. - * We extend it with the amount we can give (rate limit) */ - uint64_t ext = wanted_win - cur_win; - - ngtcp2_conn_extend_max_stream_offset(ctx->qconn, stream->id, ext); - ngtcp2_conn_extend_max_offset(ctx->qconn, ext); - stream->rx_offset_max += ext; - if(stream->rx_offset_max > stream->window_size_max) { - stream->window_size_max = stream->rx_offset_max; - CURL_TRC_CF(data, cf, "[%" PRId64 "] max window now -> %" PRIu64, - stream->id, stream->window_size_max); + if(wanted_win > cur_win) { + uint64_t delta = wanted_win - cur_win; + + if(UINT64_MAX - delta < stream->rx_offset_max) + delta = UINT64_MAX - stream->rx_offset_max; + if(delta) { + CURL_TRC_CF(data, cf, "[%" PRId64 "] rx window, extend %" PRIu64 + " (buffered %zu) by %" PRIu64 " bytes", + stream->id, cur_win, rx_buffered, delta); + stream->rx_offset_max += delta; + ngtcp2_conn_extend_max_stream_offset(ctx->qconn, stream->id, delta); + ngtcp2_conn_extend_max_offset(ctx->qconn, delta); } - CURL_TRC_CF(data, cf, "[%" PRId64 "] rx_offset_max -> %" PRIu64 - " (ext %" PRIu64 ", win %" PRIu64 ")", - stream->id, stream->rx_offset_max, ext, wanted_win); } } @@ -344,9 +330,7 @@ static int cb_h3_proxy_recv_data(nghttp3_conn *conn, int64_t stream3_id, return NGHTTP3_ERR_CALLBACK_FAILURE; } - /* DATA has been moved into our local recv buffer. Update stream offsets - * and give QUIC read credit back so long transfers over proxy tunnels - * do not stall on stream/connection flow-control limits. */ + /* DATA has been moved into our local recv buffer. Update stream offsets. */ stream->rx_offset += buflen; if(stream->rx_offset_max < stream->rx_offset) stream->rx_offset_max = stream->rx_offset; @@ -845,6 +829,7 @@ static CURLcode cf_h3_proxy_recv(struct Curl_cfilter *cf, result = Curl_bufq_cread(&pctx->tunnel.recvbuf, buf, len, pnread); if(result) goto out; + cf_h3_proxy_upd_rx_win(cf, data, pctx->tunnel.stream); } result = Curl_cf_ngtcp2_progress_ingress(cf, data, &pktx); @@ -859,6 +844,7 @@ static CURLcode cf_h3_proxy_recv(struct Curl_cfilter *cf, stream->id, len, *pnread, (int)result); goto out; } + cf_h3_proxy_upd_rx_win(cf, data, pctx->tunnel.stream); } if(*pnread) { diff --git a/tests/http/test_60_h3_proxy.py b/tests/http/test_60_h3_proxy.py index 650644eb0d5a..9b9131ae3592 100644 --- a/tests/http/test_60_h3_proxy.py +++ b/tests/http/test_60_h3_proxy.py @@ -428,6 +428,7 @@ def test_60_08_large_upload(self, env: Env, httpd, h2o_server, h2o_proxy): @MARK_NEEDS_PROXY_HTTP3 @MARK_NEEDS_H2O + @pytest.mark.skipif(condition=not Env.have_h2_curl(), reason="curl without h2") def test_60_09_parallel_downloads(self, env: Env, h2o_server, h2o_proxy): _require_available(h2o_server=h2o_server, h2o_proxy=h2o_proxy) env.make_data_file(indir=h2o_server.docs_dir, fname="download-1m", fsize=1 * 1024 * 1024) @@ -437,7 +438,7 @@ def test_60_09_parallel_downloads(self, env: Env, h2o_server, h2o_proxy): proxy_args = _h2o_proxy_args(env, h2o_proxy, "h3", tunnel=True) proxy_args.extend(["--parallel", "--parallel-max", f"{count}"]) r = curl.http_download( - urls=[urln], alpn_proto="http/1.1", with_stats=True, extra_args=proxy_args + urls=[urln], alpn_proto="h2", with_stats=True, extra_args=proxy_args ) r.check_response(count=count, http_status=200) From f71efc7a8bc3bd841017aaa58ca3b2386e609509 Mon Sep 17 00:00:00 2001 From: Joshua Rogers Date: Tue, 15 Sep 2026 23:20:57 +0200 Subject: [PATCH 202/481] vdns: announce resolver start before the HTTPS RR query is sent In the mixed threaded-resolver + c-ares configuration, Curl_async_getaddrinfo() called async_rr_start() - which initializes the c-ares channel and immediately dispatches the HTTPS RR query with ares_query_dnsrec() - before calling Curl_resolv_announce_start(). This let the HTTPS RR query reach the wire before the application-installed CURLOPT_RESOLVER_START_FUNCTION callback got a chance to configure the channel or veto the resolve, unlike the regular A/AAAA queries and the pure c-ares backend, which both already announce before querying. Split the c-ares channel setup (async_rr_init) from submitting the query (async_rr_start), and only submit it after Curl_resolv_announce_start() has returned success, so a non-zero return from the callback prevents the query from ever being sent. Edited-by: Daniel Stenberg Reported-by: Joshua Rogers Closes #23037 --- .../opts/CURLOPT_RESOLVER_START_FUNCTION.md | 5 +- lib/vdns/asyn-thrdd.c | 53 ++++-- tests/data/Makefile.am | 1 + tests/data/test464 | 32 ++++ tests/libtest/Makefile.inc | 1 + tests/libtest/lib464.c | 175 ++++++++++++++++++ 6 files changed, 248 insertions(+), 19 deletions(-) create mode 100644 tests/data/test464 create mode 100644 tests/libtest/lib464.c diff --git a/docs/libcurl/opts/CURLOPT_RESOLVER_START_FUNCTION.md b/docs/libcurl/opts/CURLOPT_RESOLVER_START_FUNCTION.md index 04e3b0d1b23e..e9961d7c3121 100644 --- a/docs/libcurl/opts/CURLOPT_RESOLVER_START_FUNCTION.md +++ b/docs/libcurl/opts/CURLOPT_RESOLVER_START_FUNCTION.md @@ -34,7 +34,10 @@ Pass a pointer to your callback function, which should match the prototype shown above. This callback function gets called by libcurl every time before a new resolve -request is started. +request is started. A single transfer can trigger more than one such +request, for example an HTTPS RR lookup is a separate request from the +regular A/AAAA lookup, and each gets its own call to this callback before it +is started. *resolver_state* points to a backend-specific resolver state. Currently only the ares resolver backend has a resolver state. It can be used to set up any diff --git a/lib/vdns/asyn-thrdd.c b/lib/vdns/asyn-thrdd.c index 1ca217a79e60..8fb5b1a5ec0f 100644 --- a/lib/vdns/asyn-thrdd.c +++ b/lib/vdns/asyn-thrdd.c @@ -212,39 +212,48 @@ static void async_thrdd_rr_done(void *user_data, ares_status_t status, async->result = Curl_httpsrr_from_ares(dnsrec, &async->httpsrr); } -static CURLcode async_rr_start(struct Curl_easy *data, - struct Curl_resolv_async *async) +/* Initialize the c-ares channel used for the HTTPS RR side-query, without + * submitting any query on it yet. */ +static CURLcode async_rr_init(struct Curl_easy *data, + struct Curl_resolv_async *async) { struct async_thrdd_ctx *thrdd = &async->thrdd; - char *https_name = NULL; int status; - CURLcode result = CURLE_OK; + (void)data; DEBUGASSERT(!thrdd->rr.channel); - if(async->peer->port != 443) { - https_name = curl_maprintf("_%u._https.%s", - async->peer->port, async->peer->hostname); - if(!https_name) { - result = CURLE_OUT_OF_MEMORY; - goto out; - } - } status = ares_init_options(&thrdd->rr.channel, NULL, 0); if(status != ARES_SUCCESS) { thrdd->rr.channel = NULL; - result = CURLE_FAILED_INIT; - goto out; + return CURLE_FAILED_INIT; } #ifdef DEBUGBUILD if(getenv("CURL_DNS_SERVER")) { const char *servers = getenv("CURL_DNS_SERVER"); status = ares_set_servers_ports_csv(thrdd->rr.channel, servers); - if(status) { - result = CURLE_FAILED_INIT; + if(status) + return CURLE_FAILED_INIT; + } +#endif + return CURLE_OK; +} + +/* Submit the HTTPS RR query on the already initialized channel. */ +static CURLcode async_rr_start(struct Curl_easy *data, + struct Curl_resolv_async *async) +{ + struct async_thrdd_ctx *thrdd = &async->thrdd; + char *https_name = NULL; + CURLcode result = CURLE_OK; + + if(async->peer->port != 443) { + https_name = curl_maprintf("_%u._https.%s", + async->peer->port, async->peer->hostname); + if(!https_name) { + result = CURLE_OUT_OF_MEMORY; goto out; } } -#endif async->queries_ongoing++; ares_query_dnsrec(thrdd->rr.channel, @@ -639,7 +648,7 @@ CURLcode Curl_async_getaddrinfo(struct Curl_easy *data, #ifdef USE_HTTPSRR_ARES DEBUGASSERT(!async->thrdd.rr.channel); if((async->dns_queries & CURL_DNSQ_HTTPS) && !async->is_ipaddr) { - result = async_rr_start(data, async); + result = async_rr_init(data, async); if(result) goto out; resolver = async->thrdd.rr.channel; @@ -650,6 +659,14 @@ CURLcode Curl_async_getaddrinfo(struct Curl_easy *data, if(result) return result; +#ifdef USE_HTTPSRR_ARES + if(resolver) { + result = async_rr_start(data, async); + if(result) + goto out; + } +#endif + #ifdef CURLRES_IPV6 /* Do not start an AAAA query for an IPv4 address when * we will start an A query for it. */ diff --git a/tests/data/Makefile.am b/tests/data/Makefile.am index 068cb2aec700..279d1699ec29 100644 --- a/tests/data/Makefile.am +++ b/tests/data/Makefile.am @@ -486,6 +486,7 @@ TESTCASES = \ test461 \ test462 \ test463 \ + test464 \ test467 \ test468 \ test469 \ diff --git a/tests/data/test464 b/tests/data/test464 new file mode 100644 index 000000000000..3ef4e463b615 --- /dev/null +++ b/tests/data/test464 @@ -0,0 +1,32 @@ + + + + +DNS +HTTPS RR +libtest + + + +# Client-side + + +Debug +asyn-rr + + +lib%TESTNUMBER + + +https://localhost:%NOLISTENPORT/464 + + + +resolver start callback vetoes the HTTPS RR query before it is sent + + + +# Verify data after the test has been "shot" + + + diff --git a/tests/libtest/Makefile.inc b/tests/libtest/Makefile.inc index 2dd93891a50d..0a545ece8b2a 100644 --- a/tests/libtest/Makefile.inc +++ b/tests/libtest/Makefile.inc @@ -68,6 +68,7 @@ TESTS_C = \ cli_ws_pingpong.c \ cli_ws_write_err.c \ \ + lib464.c \ lib500.c \ lib501.c \ lib502.c \ diff --git a/tests/libtest/lib464.c b/tests/libtest/lib464.c new file mode 100644 index 000000000000..669021a1a9fa --- /dev/null +++ b/tests/libtest/lib464.c @@ -0,0 +1,175 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +/* + * Verify that the resolver-start callback (CURLOPT_RESOLVER_START_FUNCTION) + * is announced, and can veto the resolve, before the HTTPS RR side-query is + * sent out on the wire. This binds a UDP socket of its own, points the + * c-ares HTTPS RR channel at it via the CURL_DNS_SERVER debug hook, and + * makes the callback abort the moment it sees the HTTPS RR resolver state. + * If the query had already been dispatched, this local "DNS server" would + * have received it regardless of the callback's veto. + */ + +#include "first.h" + +#ifdef HAVE_NETINET_IN_H +#include +#endif +#ifdef HAVE_ARPA_INET_H +#include +#endif + +static int t464_cb_count = 0; +static int t464_https_seen = 0; + +static int t464_resolver_start_cb(void *resolver_state, void *reserved, + void *userdata) +{ + (void)reserved; + (void)userdata; + t464_cb_count++; + if(resolver_state) { + /* This is the HTTPS RR side-channel announce: veto it. */ + t464_https_seen = 1; + return 1; + } + /* This is the regular A/AAAA announce: let it proceed. */ + return 0; +} + +static CURLcode test_lib464(const char *URL) +{ + CURL *curl = NULL; + CURLcode result = CURLE_OK; + curl_socket_t dnssock = CURL_SOCKET_BAD; + struct sockaddr_in sa; + curl_socklen_t salen = sizeof(sa); + char envbuf[64]; + char inbuf[512]; + fd_set fds; + struct timeval tv; + int port; + int gotpacket = 0; + bool global_inited = FALSE; + + dnssock = CURL_SOCKET(AF_INET, SOCK_DGRAM, 0); + if(dnssock == CURL_SOCKET_BAD) { + curl_mfprintf(stderr, "socket creation error\n"); + return TEST_ERR_MAJOR_BAD; + } + + memset(&sa, 0, sizeof(sa)); + sa.sin_family = AF_INET; + sa.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + sa.sin_port = 0; + + if(bind(dnssock, (struct sockaddr *)&sa, sizeof(sa))) { + curl_mfprintf(stderr, "bind() failed\n"); + result = TEST_ERR_MAJOR_BAD; + goto test_cleanup; + } + if(getsockname(dnssock, (struct sockaddr *)&sa, &salen)) { + curl_mfprintf(stderr, "getsockname() failed\n"); + result = TEST_ERR_MAJOR_BAD; + goto test_cleanup; + } + port = ntohs(sa.sin_port); + curl_msnprintf(envbuf, sizeof(envbuf), "127.0.0.1:%d", port); +#ifdef _WIN32 + _putenv_s("CURL_DNS_SERVER", envbuf); +#else + setenv("CURL_DNS_SERVER", envbuf, 1); +#endif + + if(curl_global_init(CURL_GLOBAL_ALL) != CURLE_OK) { + curl_mfprintf(stderr, "curl_global_init() failed\n"); + result = TEST_ERR_MAJOR_BAD; + goto test_cleanup; + } + global_inited = TRUE; + curl = curl_easy_init(); + if(!curl) { + curl_mfprintf(stderr, "curl_easy_init() failed\n"); + result = TEST_ERR_MAJOR_BAD; + goto test_cleanup; + } + + easy_setopt(curl, CURLOPT_URL, URL); + easy_setopt(curl, CURLOPT_VERBOSE, 1L); + easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L); + easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L); + easy_setopt(curl, CURLOPT_RESOLVER_START_FUNCTION, t464_resolver_start_cb); + + /* Vetoing the HTTPS RR side-query only drops that optional lookup, it + * does not fail the transfer: the plain A/AAAA resolve still succeeds + * and curl goes on to (fail to) connect, since nothing listens on the + * target port. */ + result = curl_easy_perform(curl); + if(result != CURLE_COULDNT_CONNECT) { + curl_mfprintf(stderr, "curl_easy_perform should have returned " + "CURLE_COULDNT_CONNECT but instead returned error %d\n", + (int)result); + if(result == CURLE_OK) + result = TEST_ERR_FAILURE; + goto test_cleanup; + } + result = CURLE_OK; + + if(!t464_https_seen) { + curl_mfprintf(stderr, "the HTTPS RR resolver-start announce never " + "happened, this test needs asyn-rr support\n"); + result = TEST_ERR_FAILURE; + goto test_cleanup; + } + if(t464_cb_count != 2) { + curl_mfprintf(stderr, "Unexpected number of callbacks: %d\n", + t464_cb_count); + result = TEST_ERR_FAILURE; + goto test_cleanup; + } + + FD_ZERO(&fds); + FD_SET(dnssock, &fds); + tv.tv_sec = 0; + tv.tv_usec = 200000; + if(select((int)dnssock + 1, &fds, NULL, NULL, &tv) > 0) + gotpacket = (recvfrom(dnssock, inbuf, sizeof(inbuf), 0, NULL, NULL) > 0); + + if(gotpacket) { + curl_mfprintf(stderr, "the HTTPS RR query was sent on the wire before " + "the resolver-start callback could veto it\n"); + result = TEST_ERR_FAILURE; + goto test_cleanup; + } + +test_cleanup: + curl_easy_cleanup(curl); + if(global_inited) + curl_global_cleanup(); + if(dnssock != CURL_SOCKET_BAD) + sclose(dnssock); + + return result; +} From 501a89d354fc31fde9415a38bbee135ffca3243d Mon Sep 17 00:00:00 2001 From: Daniel Stenberg Date: Tue, 22 Sep 2026 13:16:56 +0200 Subject: [PATCH 203/481] TODO: TLS channel bindings exists Reported-by: Sebastian Raase Fixes #23029 Closes #23041 --- docs/TODO.md | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/docs/TODO.md b/docs/TODO.md index 05702598f7ff..859e684cb551 100644 --- a/docs/TODO.md +++ b/docs/TODO.md @@ -449,23 +449,6 @@ the communicating parties. [curl issue 5081](https://github.com/curl/curl/issues/5081) -## TLS channel binding - -TLS 1.2 and 1.3 provide the ability to extract some secret data from the TLS -connection and use it in the client request (usually in some sort of -authentication) to ensure that the data sent is bound to the specific TLS -connection and cannot be successfully intercepted by a proxy. This -functionality can be used in a standard authentication mechanism such as -GSS-API or SCRAM, or in custom approaches like custom HTTP Authentication -headers. - -For TLS 1.2, the binding type is usually `tls-unique`, and for TLS 1.3 it is -`tls-exporter`. - -- https://datatracker.ietf.org/doc/html/rfc5929 -- https://datatracker.ietf.org/doc/html/rfc9266 -- [curl issue 9226](https://github.com/curl/curl/issues/9226) - ## Defeat TLS fingerprinting By changing the order of TLS extensions provided in the TLS handshake, it is From 47d2019fb0df50443f5e6e6e84142e1c8d0c701e Mon Sep 17 00:00:00 2001 From: Joshua Rogers Date: Mon, 21 Sep 2026 09:58:26 +0200 Subject: [PATCH 204/481] socks: track SOCKS5 authentication per socket, not per connection The bit that CURLAUTH_ONLY uses to refuse reuse of an unauthenticated connection was set by any SOCKS5 filter on the connection. The FTP data connection negotiates its own tunnel, so a proxy answering "no auth" for the control connection and an authenticated method for the data one left the bit set on a control connection that never authenticated. Only the primary socket decides, and the bit is cleared when it starts a new negotiation. Add test 1745, and a socksd "method2" keyword to answer the second connection differently. Closes #23014 --- lib/socks.c | 10 ++++-- lib/urldata.h | 2 +- tests/data/Makefile.am | 1 + tests/data/test1745 | 61 +++++++++++++++++++++++++++++++ tests/libtest/Makefile.inc | 1 + tests/libtest/lib1745.c | 74 ++++++++++++++++++++++++++++++++++++++ tests/server/socksd.c | 17 +++++++++ 7 files changed, 163 insertions(+), 3 deletions(-) create mode 100644 tests/data/test1745 create mode 100644 tests/libtest/lib1745.c diff --git a/lib/socks.c b/lib/socks.c index dfc4cb06b535..06e9673e0b0d 100644 --- a/lib/socks.c +++ b/lib/socks.c @@ -586,6 +586,10 @@ static CURLproxycode socks5_req0_init(struct Curl_cfilter *cf, /* disable username/password auth */ Curl_creds_unlink(&sx->creds); + if(cf->sockindex == FIRSTSOCKET) + /* the reuse of this connection depends on how this negotiation ends */ + cf->conn->bits.socks5_authenticated = FALSE; + req[0] = 5; /* version */ nauths = 0; if(!data->set.socks5_auth_only) { @@ -647,7 +651,8 @@ static CURLproxycode socks5_check_resp0(struct socks_ctx *sx, return CURLPX_OK; case 1: if(data->set.socks5auth & CURLAUTH_GSSAPI) { - cf->conn->bits.socks5_authenticated = TRUE; + if(cf->sockindex == FIRSTSOCKET) + cf->conn->bits.socks5_authenticated = TRUE; sxstate(sx, cf, data, SOCKS5_ST_GSSAPI_INIT); return CURLPX_OK; } @@ -656,7 +661,8 @@ static CURLproxycode socks5_check_resp0(struct socks_ctx *sx, case 2: /* regular name + password authentication */ if(data->set.socks5auth & CURLAUTH_BASIC) { - cf->conn->bits.socks5_authenticated = TRUE; + if(cf->sockindex == FIRSTSOCKET) + cf->conn->bits.socks5_authenticated = TRUE; sxstate(sx, cf, data, SOCKS5_ST_AUTH_INIT); return CURLPX_OK; } diff --git a/lib/urldata.h b/lib/urldata.h index d2c025408548..4da67561f6f6 100644 --- a/lib/urldata.h +++ b/lib/urldata.h @@ -184,7 +184,7 @@ struct ConnectBits { BIT(connect_only); #ifndef CURL_DISABLE_PROXY BIT(origin_is_proxy); /* if set, the connection's origin is a proxy */ - BIT(socks5_authenticated); /* SOCKS5 negotiated an authentication method */ + BIT(socks5_authenticated); /* SOCKS5 authenticated on FIRSTSOCKET */ #endif /* always modify bits.close with the connclose() and connkeep() macros! */ BIT(close); /* if set, we close the connection after this request */ diff --git a/tests/data/Makefile.am b/tests/data/Makefile.am index 279d1699ec29..96af28719eed 100644 --- a/tests/data/Makefile.am +++ b/tests/data/Makefile.am @@ -1742,6 +1742,7 @@ TESTCASES = \ test1742 \ test1743 \ test1744 \ + test1745 \ test1790 \ test1791 \ test1800 \ diff --git a/tests/data/test1745 b/tests/data/test1745 new file mode 100644 index 000000000000..957ae7bee1b2 --- /dev/null +++ b/tests/data/test1745 @@ -0,0 +1,61 @@ + + + + +FTP +PASV +RETR +SOCKS5 + + + +# Server-side + + +silly content + + +method 0 +method2 2 +user silly +password person + + + +# Client-side + + +proxy + + +ftp +socks5 + + +lib%TESTNUMBER + + +SOCKS5 CURLAUTH_ONLY ignores auth done for the FTP data socket + + +ftp://%HOSTIP:%FTPPORT/%TESTNUMBER %HOSTIP:%SOCKSPORT + + + +# Verify data after the test has been "shot" + + +97 + + +USER anonymous +PASS ftp@example.com +PWD +EPSV +TYPE I +SIZE %TESTNUMBER +RETR %TESTNUMBER +QUIT + + + diff --git a/tests/libtest/Makefile.inc b/tests/libtest/Makefile.inc index 0a545ece8b2a..b3a4cf8fe8ca 100644 --- a/tests/libtest/Makefile.inc +++ b/tests/libtest/Makefile.inc @@ -248,6 +248,7 @@ TESTS_C = \ lib1686.c \ lib1742.c \ lib1744.c \ + lib1745.c \ lib1791.c \ lib1900.c \ lib1901.c \ diff --git a/tests/libtest/lib1745.c b/tests/libtest/lib1745.c new file mode 100644 index 000000000000..7126ab5bfa7a --- /dev/null +++ b/tests/libtest/lib1745.c @@ -0,0 +1,74 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ +/* + * argv1 = FTP URL + * argv2 = SOCKS5 proxy + */ + +#include "first.h" + +static CURLcode test_lib1745(const char *URL) +{ + CURLcode result = CURLE_OK; + CURL *curl = NULL; + + if(test_argc < 2) + return TEST_ERR_MAJOR_BAD; + + res_global_init(CURL_GLOBAL_ALL); + if(result) + return result; + + curl = curl_easy_init(); + if(!curl) { + curl_mfprintf(stderr, "curl_easy_init() failed\n"); + curl_global_cleanup(); + return TEST_ERR_MAJOR_BAD; + } + + start_test_timing(); + + easy_setopt(curl, CURLOPT_URL, URL); + easy_setopt(curl, CURLOPT_PROXY, libtest_arg2); + easy_setopt(curl, CURLOPT_PROXYTYPE, (long)CURLPROXY_SOCKS5); + easy_setopt(curl, CURLOPT_PROXYUSERPWD, "silly:person"); + + /* the proxy picks no authentication for the control connection and + username/password for the data connection */ + result = curl_easy_perform(curl); + if(result) + goto test_cleanup; + + /* the control connection did not authenticate, so it must not be + reused for this one */ + easy_setopt(curl, CURLOPT_SOCKS5_AUTH, + (long)(CURLAUTH_BASIC | CURLAUTH_ONLY)); + result = curl_easy_perform(curl); + +test_cleanup: + + curl_easy_cleanup(curl); + curl_global_cleanup(); + return result; +} diff --git a/tests/server/socksd.c b/tests/server/socksd.c index cd0bc2b9008c..1a846756d3f3 100644 --- a/tests/server/socksd.c +++ b/tests/server/socksd.c @@ -49,6 +49,8 @@ * 0 - no auth * 1 - GSSAPI (not supported) * 2 - user + password + * "method2 [number]" - connect method to respond with on the second + * connection, defaults to the "method" one * "response [number]" - the decimal number to respond to a connect * SOCKS5: 0 is OK, SOCKS4: 90 is ok * @@ -58,6 +60,7 @@ static const char *backendaddr = "127.0.0.1"; static uint16_t backendport = 0; /* default is use client's */ +static unsigned int connections; /* number of clients served */ struct socksd_configurable { unsigned char version; /* initial version byte in the request must match @@ -66,6 +69,7 @@ struct socksd_configurable { unsigned char nmethods_max; /* maximum number of nmethods to expect */ unsigned char responseversion; unsigned char responsemethod; + unsigned char responsemethod2; unsigned char reqcmd; unsigned char connectrep; uint16_t port; /* backend port */ @@ -79,6 +83,7 @@ struct socksd_configurable { #define CONFIG_NMETHODS_MAX 3 #define CONFIG_RESPONSEVERSION CONFIG_VERSION #define CONFIG_RESPONSEMETHOD 0 /* no auth */ +#define CONFIG_RESPONSEMETHOD2 0xff /* unset, use CONFIG_RESPONSEMETHOD */ #define CONFIG_REQCMD 1 /* CONNECT */ #define CONFIG_PORT backendport #define CONFIG_ADDR backendaddr @@ -96,6 +101,7 @@ static void socksd_resetdefaults(void) s_config.nmethods_max = CONFIG_NMETHODS_MAX; s_config.responseversion = CONFIG_RESPONSEVERSION; s_config.responsemethod = CONFIG_RESPONSEMETHOD; + s_config.responsemethod2 = CONFIG_RESPONSEMETHOD2; s_config.reqcmd = CONFIG_REQCMD; s_config.connectrep = CONFIG_CONNECTREP; s_config.port = CONFIG_PORT; @@ -175,6 +181,13 @@ static void socksd_getconfig(void) logmsg("method [%d] set", s_config.responsemethod); } } + else if(!strcmp(key, "method2")) { + pval = value; + if(!curlx_str_number(&pval, &num, 0xff)) { + s_config.responsemethod2 = (unsigned char)num; + logmsg("method2 [%d] set", s_config.responsemethod2); + } + } else if(!strcmp(key, "response")) { pval = value; if(!curlx_str_number(&pval, &num, 0xff)) { @@ -303,6 +316,10 @@ static curl_socket_t sockit(curl_socket_t fd) socksd_getconfig(); + if((++connections == 2) && (s_config.responsemethod2 != + CONFIG_RESPONSEMETHOD2)) + s_config.responsemethod = s_config.responsemethod2; + rc = sread(fd, buffer, sizeof(buffer)); if(rc <= 0) { logmsg("SOCKS identifier message missing, recv returned %zd", rc); From 948486425911bc83cc8a224bf23bcffaaa7de6f6 Mon Sep 17 00:00:00 2001 From: Joshua Rogers Date: Mon, 21 Sep 2026 18:19:40 +0200 Subject: [PATCH 205/481] socksd: count connections per test, widen the method2 sentinel --- tests/server/socksd.c | 30 ++++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/tests/server/socksd.c b/tests/server/socksd.c index 1a846756d3f3..4b721e111040 100644 --- a/tests/server/socksd.c +++ b/tests/server/socksd.c @@ -50,7 +50,7 @@ * 1 - GSSAPI (not supported) * 2 - user + password * "method2 [number]" - connect method to respond with on the second - * connection, defaults to the "method" one + * connection of the test, defaults to the "method" one * "response [number]" - the decimal number to respond to a connect * SOCKS5: 0 is OK, SOCKS4: 90 is ok * @@ -60,7 +60,8 @@ static const char *backendaddr = "127.0.0.1"; static uint16_t backendport = 0; /* default is use client's */ -static unsigned int connections; /* number of clients served */ +static unsigned int connections; /* clients served in the current test */ +static int last_testnum = -1; /* the test the connections belong to */ struct socksd_configurable { unsigned char version; /* initial version byte in the request must match @@ -69,7 +70,8 @@ struct socksd_configurable { unsigned char nmethods_max; /* maximum number of nmethods to expect */ unsigned char responseversion; unsigned char responsemethod; - unsigned char responsemethod2; + int responsemethod2; /* negative means unset */ + int testnum; unsigned char reqcmd; unsigned char connectrep; uint16_t port; /* backend port */ @@ -83,7 +85,7 @@ struct socksd_configurable { #define CONFIG_NMETHODS_MAX 3 #define CONFIG_RESPONSEVERSION CONFIG_VERSION #define CONFIG_RESPONSEMETHOD 0 /* no auth */ -#define CONFIG_RESPONSEMETHOD2 0xff /* unset, use CONFIG_RESPONSEMETHOD */ +#define CONFIG_RESPONSEMETHOD2 (-1) /* unset, use CONFIG_RESPONSEMETHOD */ #define CONFIG_REQCMD 1 /* CONNECT */ #define CONFIG_PORT backendport #define CONFIG_ADDR backendaddr @@ -102,6 +104,7 @@ static void socksd_resetdefaults(void) s_config.responseversion = CONFIG_RESPONSEVERSION; s_config.responsemethod = CONFIG_RESPONSEMETHOD; s_config.responsemethod2 = CONFIG_RESPONSEMETHOD2; + s_config.testnum = 0; s_config.reqcmd = CONFIG_REQCMD; s_config.connectrep = CONFIG_CONNECTREP; s_config.port = CONFIG_PORT; @@ -184,10 +187,17 @@ static void socksd_getconfig(void) else if(!strcmp(key, "method2")) { pval = value; if(!curlx_str_number(&pval, &num, 0xff)) { - s_config.responsemethod2 = (unsigned char)num; + s_config.responsemethod2 = (int)num; logmsg("method2 [%d] set", s_config.responsemethod2); } } + else if(!strcmp(key, "Testnum")) { + pval = value; + if(!curlx_str_number(&pval, &num, INT_MAX)) { + s_config.testnum = (int)num; + logmsg("testnum = %d", s_config.testnum); + } + } else if(!strcmp(key, "response")) { pval = value; if(!curlx_str_number(&pval, &num, 0xff)) { @@ -316,9 +326,13 @@ static curl_socket_t sockit(curl_socket_t fd) socksd_getconfig(); - if((++connections == 2) && (s_config.responsemethod2 != - CONFIG_RESPONSEMETHOD2)) - s_config.responsemethod = s_config.responsemethod2; + if(last_testnum != s_config.testnum) { + last_testnum = s_config.testnum; + connections = 0; + } + + if((++connections == 2) && (s_config.responsemethod2 >= 0)) + s_config.responsemethod = (unsigned char)s_config.responsemethod2; rc = sread(fd, buffer, sizeof(buffer)); if(rc <= 0) { From 2302acd928168ae046a245475ee0f52d125cf6c0 Mon Sep 17 00:00:00 2001 From: Stefan Eissing Date: Tue, 22 Sep 2026 09:35:09 +0200 Subject: [PATCH 206/481] tool: cache header scheme In the header callback, cache the scheme of the current request, resetting this after an empty header for scheme changes after a redirect. Closes #23031 --- src/tool_cb_hdr.c | 36 ++++++++++++++++++------------------ src/tool_operate.c | 1 + src/tool_operate.h | 1 + 3 files changed, 20 insertions(+), 18 deletions(-) diff --git a/src/tool_cb_hdr.c b/src/tool_cb_hdr.c index c9bc8978edd8..bd846062a0de 100644 --- a/src/tool_cb_hdr.c +++ b/src/tool_cb_hdr.c @@ -432,7 +432,6 @@ size_t tool_header_cb(char *ptr, size_t size, size_t nmemb, void *userdata) const char *str = ptr; const size_t cb = size * nmemb; const char *end = ptr + cb; - const char *scheme = NULL; bool cd_checked = FALSE; if(!per->config) @@ -451,6 +450,16 @@ size_t tool_header_cb(char *ptr, size_t size, size_t nmemb, void *userdata) memset(outs->utf8seq, 0, sizeof(outs->utf8seq)); #endif + /* If the last header was empty, check scheme again as we follow + * protocol switches on redirects */ + if(!per->out_scheme || per->was_last_header_empty) { + char *scheme; + curl_easy_getinfo(per->curl, CURLINFO_SCHEME, &scheme); + per->out_scheme = proto_token(scheme); + per->num_headers = 0; + } + per->was_last_header_empty = (ptr[0] == '\r' || ptr[0] == '\n'); + /* * Write header data when curl option --dump-header (-D) is given. */ @@ -466,9 +475,7 @@ size_t tool_header_cb(char *ptr, size_t size, size_t nmemb, void *userdata) } } - curl_easy_getinfo(per->curl, CURLINFO_SCHEME, &scheme); - scheme = proto_token(scheme); - if((scheme == proto_http || scheme == proto_https)) { + if((per->out_scheme == proto_http || per->out_scheme == proto_https)) { long response = 0; curl_easy_getinfo(per->curl, CURLINFO_RESPONSE_CODE, &response); @@ -498,20 +505,13 @@ size_t tool_header_cb(char *ptr, size_t size, size_t nmemb, void *userdata) cd_checked = TRUE; } } - if(hdrcbdata->config->writeout) { - const char *value = memchr(ptr, ':', cb); - if(value) { - if(per->was_last_header_empty) - per->num_headers = 0; - per->was_last_header_empty = FALSE; - per->num_headers++; - } - else if(ptr[0] == '\r' || ptr[0] == '\n') - per->was_last_header_empty = TRUE; - } + + if(hdrcbdata->config->writeout && memchr(ptr, ':', cb)) + per->num_headers++; + if(hdrcbdata->config->show_headers && !outs->out_null && - (scheme == proto_http || scheme == proto_https || - scheme == proto_rtsp || scheme == proto_file)) { + (per->out_scheme == proto_http || per->out_scheme == proto_https || + per->out_scheme == proto_rtsp || per->out_scheme == proto_file)) { /* bold headers only for selected protocols */ const char *value = NULL; @@ -519,7 +519,7 @@ size_t tool_header_cb(char *ptr, size_t size, size_t nmemb, void *userdata) bypass the content-disposition handling above. Do not open the output file until the filename decision is complete. Only for HTTP(S). */ if(hdrcbdata->honor_cd_filename && !cd_checked && - (scheme == proto_http || scheme == proto_https)) + (per->out_scheme == proto_http || per->out_scheme == proto_https)) return buffer_header(hdrcbdata, str, cb); if(!outs->stream && !tool_create_output_file(outs, per->config)) diff --git a/src/tool_operate.c b/src/tool_operate.c index 0d689455c381..b5b3d6ad8c92 100644 --- a/src/tool_operate.c +++ b/src/tool_operate.c @@ -1431,6 +1431,7 @@ static CURLcode create_single(struct OperationConfig *config, per->config = config; per->curl = curl; per->urlnum = u->num; + per->out_scheme = NULL; /* default headers output stream is stdout */ heads = &per->heads; diff --git a/src/tool_operate.h b/src/tool_operate.h index 69b304dfc030..7330f7b778e6 100644 --- a/src/tool_operate.h +++ b/src/tool_operate.h @@ -46,6 +46,7 @@ struct per_transfer { char *url; curl_off_t urlnum; /* the index of the given URL */ char *outfile; + const char *out_scheme; /* scheme of current header output */ int infd; struct ProgressData progressbar; struct OutStruct outs; From b9cbb61a53713bf41c89815000a8f1565ece1ac1 Mon Sep 17 00:00:00 2001 From: Daniel Stenberg Date: Tue, 15 Sep 2026 23:18:13 +0200 Subject: [PATCH 207/481] openldap: hand it a dup'd socket, not curl's own oldap_connect() passed the connection filter's live descriptor directly to ldap_init_fd(). For plaintext ldap:// connections, no Sockbuf shim gets installed to intercept the SDK's teardown (that only happens once TLS is up), so ldap_unbind_ext() runs the SDK's normal close() on that descriptor. The generic socket cfilter still owns the same numeric descriptor and closes it again afterward. If the descriptor number gets reused by another thread in between, the second close() tears down something unrelated. Give ldap_init_fd() a dup of the descriptor instead of the original. The SDK now owns and closes only its own copy; the connection filter still owns and closes the original exactly once. This also protects the oldap_conn_dtor() teardown path, which calls ldap_unbind_ext() the same way. Close the dup'd descriptor (dupfd / HANDLE) when ldap_init_fd() itself returns an error. Upstream's ldap_init_fd() reports every such failure before the descriptor is ever attached to the SDK's Sockbuf, so on error neither the SDK nor curl takes ownership of it; without the extra close it leaked one descriptor per failed LDAP connection attempt. No test added: curl's test suite has no LDAP server harness to spin up a real slapd instance against, matching prior LDAP-backend fixes in this codebase. Verified instead with a local slapd (OpenLDAP 2.7.1): with a close()/socket() interposer tracking descriptor lifetimes, a plaintext ldap:// request against the unpatched code shows the SDK closing the shared descriptor followed by a second, failing close() from curl (EBADF); with this fix, the SDK's close lands on its own dup and curl's own close of the original succeeds without a prior close from the SDK. The ldap_init_fd() failure path was verified separately, linking directly against libldap: a malformed URL makes ldap_init_fd() fail before it touches the Sockbuf, the dup'd descriptor is confirmed still open afterward on the unpatched path, and confirmed closed with this fix. Reported-by: Joshua Rogers --- lib/openldap.c | 55 +++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 52 insertions(+), 3 deletions(-) diff --git a/lib/openldap.c b/lib/openldap.c index 8f2651294a79..f67c841e6ac8 100644 --- a/lib/openldap.c +++ b/lib/openldap.c @@ -100,6 +100,7 @@ struct ldapconninfo { Curl_send *send; struct berval *servercred; /* SASL data from server. */ ldapstate state; /* Current machine state. */ + curl_socket_t dupfd; /* duplicated socket */ int proto; /* LDAP_PROTO_TCP/LDAP_PROTO_UDP/LDAP_PROTO_IPC */ int msgid; /* Current message id. */ }; @@ -425,10 +426,21 @@ static int ldapsb_tls_remove(Sockbuf_IO_Desc *sbiod) return 0; } -/* We do not need to do anything because libcurl does it already */ +/* if there is a duplicated socket to close, close it */ static int ldapsb_tls_close(Sockbuf_IO_Desc *sbiod) { - (void)sbiod; + struct Curl_easy *data = sbiod->sbiod_pvt; + if(data) { + struct connectdata *conn = data->conn; + if(conn) { + struct ldapconninfo *li = Curl_conn_meta_get(conn, CURL_META_LDAP_CONN); + if(li && (li->dupfd != CURL_SOCKET_BAD)) { + sclose(li->dupfd); + li->dupfd = CURL_SOCKET_BAD; + } + } + } + return 0; } @@ -607,6 +619,7 @@ static CURLcode oldap_connect(struct Curl_easy *data, bool *done) result = CURLE_OUT_OF_MEMORY; goto out; } + li->dupfd = CURL_SOCKET_BAD; result = Curl_conn_meta_set(conn, CURL_META_LDAP_CONN, li, oldap_conn_dtor); if(result) @@ -632,7 +645,43 @@ static CURLcode oldap_connect(struct Curl_easy *data, bool *done) goto out; } - rc = ldap_init_fd(conn->sock[FIRSTSOCKET], li->proto, hosturl, &li->ld); + { +#ifdef _WIN32 + SOCKET dupfd = INVALID_SOCKET; + WSAPROTOCOL_INFO pi; + if(WSADuplicateSocket(conn->sock[FIRSTSOCKET], GetCurrentProcessId(), + &pi)) + ; /* error */ + else + dupfd = CURL_SOCKET(pi.iAddressFamily, pi.iSocketType, pi.iProtocol); +#else +#ifdef F_DUPFD_CLOEXEC + int dupfd = fcntl(conn->sock[FIRSTSOCKET], F_DUPFD_CLOEXEC, 0); +#else + int dupfd = dup(conn->sock[FIRSTSOCKET]); +#ifdef HAVE_FCNTL + if((dupfd != CURL_SOCKET_BAD) && + (fcntl(dupfd, F_SETFD, FD_CLOEXEC) < 0)) { + sclose(dupfd); + dupfd = CURL_SOCKET_BAD; + } +#endif /* HAVE_FCNTL */ +#endif /* F_DUPFD_CLOEXEC */ +#endif /* _WIN32 */ + + if(dupfd == CURL_SOCKET_BAD) { + result = CURLE_COULDNT_CONNECT; + goto out; + } + rc = ldap_init_fd((ber_socket_t)dupfd, li->proto, hosturl, &li->ld); + if(rc) + sclose(dupfd); + else + /* store the socket, as when this is used by TLS we close this ourselves + and at this point the code does not yet know if this will use TLS or + not */ + li->dupfd = dupfd; + } if(rc) { failf(data, "LDAP local: Cannot connect to %s, %s", hosturl, ldap_err2string(rc)); From 1a712e5c6047bd73c32320f4be226bf2ea308449 Mon Sep 17 00:00:00 2001 From: Daniel Stenberg Date: Tue, 22 Sep 2026 16:32:06 +0200 Subject: [PATCH 208/481] tests: for libtests, assume a default name Since virtually all libtests invoke lib%TESTNUMBER, let's just default to that. Closes #23042 --- docs/tests/FILEFORMAT.md | 7 +++++++ tests/runner.pm | 7 ++++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/docs/tests/FILEFORMAT.md b/docs/tests/FILEFORMAT.md index 54b189f1db86..3ef4f0f81990 100644 --- a/docs/tests/FILEFORMAT.md +++ b/docs/tests/FILEFORMAT.md @@ -567,10 +567,17 @@ is skipped and the (single-line) output is displayed as reason for not running the test. ### `` + Name of tool to invoke instead of "curl". This tool must be built and exist either in the `libtest/` directory (if the tool name starts with `lib`) or in the `unit/` directory (if the tool name starts with `unit`). +If the keyword `unittest` is set in the test case, the default name for the +tool is `unit%TESTNUMBER`. + +If the keyword `libtest` is set in the test case, the default name for the +tool is `lib%TESTNUMBER`. + ### `` Brief test case description, shown when the test runs. diff --git a/tests/runner.pm b/tests/runner.pm index 5187fc094f56..36f4520ab42b 100644 --- a/tests/runner.pm +++ b/tests/runner.pm @@ -862,7 +862,7 @@ sub singletest_run { $tool = $CMDLINE; $disablevalgrind = 1; } - elsif(!$tool && !$keywords{"unittest"}) { + elsif(!$tool && (!$keywords{"unittest"} && !$keywords{"libtest"})) { # run curl, add suitable command line options my $inc = ""; if((!$cmdhash{'option'}) || ($cmdhash{'option'} !~ /no-include/)) { @@ -907,6 +907,11 @@ sub singletest_run { $tool_name = "unit$testnum"; $tool = $tool_name . exe_ext('TOOL'); } + # Default the tool to a libtest with the same number as the test + elsif($keywords{"libtest"} && !$tool) { + $tool_name = "lib$testnum"; + $tool = $tool_name . exe_ext('TOOL'); + } if($tool =~ /^lib/) { $tool = "libtests" . exe_ext('TOOL'); From 937b25fbd66ded55746e44672b5b32227ce9b3d7 Mon Sep 17 00:00:00 2001 From: Daniel Stenberg Date: Tue, 22 Sep 2026 16:55:21 +0200 Subject: [PATCH 209/481] tests: remove tool name from libtests using default Closes #23044 --- tests/data/test1156 | 3 --- tests/data/test1301 | 3 --- tests/data/test1308 | 3 --- tests/data/test1396 | 3 --- tests/data/test1398 | 3 --- tests/data/test1451 | 3 --- tests/data/test1485 | 3 --- tests/data/test1500 | 3 --- tests/data/test1501 | 3 --- tests/data/test1502 | 3 --- tests/data/test1506 | 3 --- tests/data/test1507 | 3 --- tests/data/test1508 | 3 --- tests/data/test1509 | 3 --- tests/data/test1510 | 3 --- tests/data/test1511 | 3 --- tests/data/test1512 | 3 --- tests/data/test1513 | 3 --- tests/data/test1514 | 3 --- tests/data/test1515 | 3 --- tests/data/test1517 | 3 --- tests/data/test1518 | 3 --- tests/data/test1520 | 3 --- tests/data/test1521 | 3 --- tests/data/test1522 | 3 --- tests/data/test1523 | 3 --- tests/data/test1525 | 3 --- tests/data/test1526 | 3 --- tests/data/test1527 | 3 --- tests/data/test1528 | 3 --- tests/data/test1529 | 3 --- tests/data/test1530 | 3 --- tests/data/test1531 | 3 --- tests/data/test1532 | 3 --- tests/data/test1533 | 3 --- tests/data/test1534 | 3 --- tests/data/test1535 | 3 --- tests/data/test1536 | 3 --- tests/data/test1537 | 3 --- tests/data/test1538 | 3 --- tests/data/test1540 | 3 --- tests/data/test1541 | 3 --- tests/data/test1542 | 3 --- tests/data/test1545 | 3 --- tests/data/test1549 | 3 --- tests/data/test1550 | 3 --- tests/data/test1551 | 3 --- tests/data/test1552 | 3 --- tests/data/test1553 | 3 --- tests/data/test1554 | 3 --- tests/data/test1555 | 3 --- tests/data/test1556 | 3 --- tests/data/test1557 | 3 --- tests/data/test1558 | 3 --- tests/data/test1559 | 3 --- tests/data/test1560 | 3 --- tests/data/test1564 | 3 --- tests/data/test1565 | 3 --- tests/data/test1567 | 3 --- tests/data/test1568 | 3 --- tests/data/test1569 | 3 --- tests/data/test1571 | 3 --- tests/data/test1576 | 3 --- tests/data/test1582 | 3 --- tests/data/test1587 | 3 --- tests/data/test1588 | 3 --- tests/data/test1589 | 3 --- tests/data/test1591 | 3 --- tests/data/test1592 | 3 --- tests/data/test1593 | 3 --- tests/data/test1594 | 3 --- tests/data/test1597 | 3 --- tests/data/test1598 | 3 --- tests/data/test1599 | 3 --- tests/data/test1605 | 3 --- tests/data/test1616 | 3 --- tests/data/test1647 | 3 --- tests/data/test1648 | 3 --- tests/data/test1649 | 3 --- tests/data/test1662 | 3 --- tests/data/test1678 | 3 --- tests/data/test1686 | 3 --- tests/data/test1742 | 3 --- tests/data/test1744 | 3 --- tests/data/test1745 | 4 +--- tests/data/test1791 | 3 --- tests/data/test1900 | 3 --- tests/data/test1901 | 3 --- tests/data/test1902 | 3 --- tests/data/test1903 | 3 --- tests/data/test1905 | 3 --- tests/data/test1906 | 3 --- tests/data/test1907 | 3 --- tests/data/test1908 | 3 --- tests/data/test1910 | 3 --- tests/data/test1911 | 3 --- tests/data/test1912 | 3 --- tests/data/test1913 | 3 --- tests/data/test1915 | 3 --- tests/data/test1916 | 3 --- tests/data/test1918 | 3 --- tests/data/test1919 | 3 --- tests/data/test1920 | 3 --- tests/data/test1921 | 3 --- tests/data/test1922 | 3 --- tests/data/test1933 | 3 --- tests/data/test1934 | 3 --- tests/data/test1935 | 3 --- tests/data/test1936 | 3 --- tests/data/test1937 | 3 --- tests/data/test1938 | 3 --- tests/data/test1939 | 3 --- tests/data/test1940 | 3 --- tests/data/test1945 | 3 --- tests/data/test1947 | 3 --- tests/data/test1948 | 3 --- tests/data/test1955 | 3 --- tests/data/test1956 | 3 --- tests/data/test1957 | 3 --- tests/data/test1958 | 3 --- tests/data/test1959 | 3 --- tests/data/test1960 | 3 --- tests/data/test1964 | 3 --- tests/data/test1965 | 3 --- tests/data/test1967 | 3 --- tests/data/test1970 | 3 --- tests/data/test1971 | 3 --- tests/data/test1972 | 3 --- tests/data/test1973 | 3 --- tests/data/test1974 | 3 --- tests/data/test1975 | 3 --- tests/data/test1977 | 3 --- tests/data/test1978 | 3 --- tests/data/test1985 | 3 --- tests/data/test2023 | 3 --- tests/data/test2032 | 3 --- tests/data/test2082 | 3 --- tests/data/test2118 | 3 --- tests/data/test2270 | 3 --- tests/data/test2301 | 3 --- tests/data/test2302 | 3 --- tests/data/test2304 | 3 --- tests/data/test2306 | 3 --- tests/data/test2308 | 3 --- tests/data/test2309 | 3 --- tests/data/test2397 | 4 ---- tests/data/test2402 | 3 --- tests/data/test2404 | 3 --- tests/data/test2405 | 3 --- tests/data/test2412 | 3 --- tests/data/test2414 | 3 --- tests/data/test2453 | 3 --- tests/data/test2502 | 3 --- tests/data/test2504 | 3 --- tests/data/test2505 | 3 --- tests/data/test2506 | 3 --- tests/data/test2700 | 3 --- tests/data/test2805 | 3 --- tests/data/test2924 | 3 --- tests/data/test3010 | 3 --- tests/data/test3025 | 3 --- tests/data/test3026 | 3 --- tests/data/test3027 | 3 --- tests/data/test3033 | 3 --- tests/data/test3034 | 3 --- tests/data/test3100 | 3 --- tests/data/test3101 | 3 --- tests/data/test3102 | 3 --- tests/data/test3103 | 3 --- tests/data/test3104 | 3 --- tests/data/test3105 | 3 --- tests/data/test3107 | 3 --- tests/data/test3207 | 3 --- tests/data/test3208 | 3 --- tests/data/test3231 | 3 --- tests/data/test3233 | 3 --- tests/data/test3595 | 3 --- tests/data/test3596 | 3 --- tests/data/test464 | 3 --- tests/data/test500 | 3 --- tests/data/test5000 | 3 --- tests/data/test5004 | 3 --- tests/data/test501 | 3 --- tests/data/test502 | 3 --- tests/data/test503 | 3 --- tests/data/test504 | 3 --- tests/data/test505 | 3 --- tests/data/test506 | 3 --- tests/data/test507 | 3 --- tests/data/test508 | 3 --- tests/data/test509 | 3 --- tests/data/test510 | 3 --- tests/data/test511 | 3 --- tests/data/test512 | 3 --- tests/data/test513 | 3 --- tests/data/test514 | 3 --- tests/data/test515 | 3 --- tests/data/test516 | 3 --- tests/data/test517 | 3 --- tests/data/test518 | 3 --- tests/data/test519 | 3 --- tests/data/test520 | 3 --- tests/data/test521 | 3 --- tests/data/test523 | 3 --- tests/data/test524 | 3 --- tests/data/test525 | 3 --- tests/data/test526 | 3 --- tests/data/test530 | 3 --- tests/data/test533 | 3 --- tests/data/test536 | 3 --- tests/data/test537 | 3 --- tests/data/test539 | 3 --- tests/data/test540 | 3 --- tests/data/test541 | 3 --- tests/data/test542 | 3 --- tests/data/test543 | 3 --- tests/data/test544 | 3 --- tests/data/test547 | 3 --- tests/data/test549 | 3 --- tests/data/test552 | 3 --- tests/data/test553 | 3 --- tests/data/test554 | 3 --- tests/data/test555 | 3 --- tests/data/test556 | 3 --- tests/data/test557 | 3 --- tests/data/test558 | 3 --- tests/data/test559 | 3 --- tests/data/test560 | 3 --- tests/data/test562 | 3 --- tests/data/test564 | 3 --- tests/data/test566 | 3 --- tests/data/test567 | 3 --- tests/data/test568 | 3 --- tests/data/test569 | 3 --- tests/data/test570 | 3 --- tests/data/test571 | 3 --- tests/data/test572 | 3 --- tests/data/test573 | 3 --- tests/data/test574 | 3 --- tests/data/test575 | 3 --- tests/data/test576 | 3 --- tests/data/test578 | 3 --- tests/data/test579 | 3 --- tests/data/test582 | 3 --- tests/data/test583 | 3 --- tests/data/test589 | 3 --- tests/data/test590 | 3 --- tests/data/test591 | 3 --- tests/data/test597 | 3 --- tests/data/test598 | 3 --- tests/data/test599 | 3 --- tests/data/test643 | 3 --- tests/data/test650 | 3 --- tests/data/test651 | 3 --- tests/data/test652 | 3 --- tests/data/test653 | 3 --- tests/data/test654 | 3 --- tests/data/test655 | 3 --- tests/data/test658 | 3 --- tests/data/test659 | 3 --- tests/data/test661 | 3 --- tests/data/test666 | 3 --- tests/data/test667 | 3 --- tests/data/test668 | 3 --- tests/data/test670 | 3 --- tests/data/test674 | 3 --- tests/data/test676 | 3 --- tests/data/test677 | 3 --- tests/data/test678 | 3 --- tests/data/test694 | 3 --- tests/data/test695 | 3 --- tests/data/test751 | 3 --- tests/data/test753 | 3 --- tests/data/test757 | 3 --- tests/data/test758 | 3 --- tests/data/test766 | 3 --- 276 files changed, 1 insertion(+), 829 deletions(-) diff --git a/tests/data/test1156 b/tests/data/test1156 index 8b99a817f862..b44fe6ba69c6 100644 --- a/tests/data/test1156 +++ b/tests/data/test1156 @@ -53,9 +53,6 @@ body http - -lib%TESTNUMBER - HTTP resume/range fail range-error content-range combinations diff --git a/tests/data/test1301 b/tests/data/test1301 index de4f1772b2c8..4c82deaad106 100644 --- a/tests/data/test1301 +++ b/tests/data/test1301 @@ -9,9 +9,6 @@ libtest # Client-side - -lib%TESTNUMBER - curl_strequal tests diff --git a/tests/data/test1308 b/tests/data/test1308 index ffb924ae9ae8..adf3a65051fd 100644 --- a/tests/data/test1308 +++ b/tests/data/test1308 @@ -18,9 +18,6 @@ form-api formpost tests - -lib%TESTNUMBER - %LOGDIR/test-%TESTNUMBER diff --git a/tests/data/test1396 b/tests/data/test1396 index 0ad7fa5a059b..e7f0b5d0c30e 100644 --- a/tests/data/test1396 +++ b/tests/data/test1396 @@ -10,9 +10,6 @@ libtest # Client-side - -lib%TESTNUMBER - curl_easy_escape and curl_easy_unescape diff --git a/tests/data/test1398 b/tests/data/test1398 index 30183caae4c0..10db85a4011d 100644 --- a/tests/data/test1398 +++ b/tests/data/test1398 @@ -9,9 +9,6 @@ libtest # Client-side - -lib%TESTNUMBER - curl_msnprintf tests diff --git a/tests/data/test1451 b/tests/data/test1451 index 0ca5c8b160f8..6843d5db165c 100644 --- a/tests/data/test1451 +++ b/tests/data/test1451 @@ -12,9 +12,6 @@ libtest win32 - -lib%TESTNUMBER - mprintf %I length modifier unit tests diff --git a/tests/data/test1485 b/tests/data/test1485 index a5d49ffa6276..0afe45c1004d 100644 --- a/tests/data/test1485 +++ b/tests/data/test1485 @@ -30,9 +30,6 @@ Content-Length: 7 http - -lib%TESTNUMBER - get curlinfo on last header in callback diff --git a/tests/data/test1500 b/tests/data/test1500 index 1750d887f948..e518964370a5 100644 --- a/tests/data/test1500 +++ b/tests/data/test1500 @@ -32,9 +32,6 @@ http http # tool is what to use instead of 'curl' - -lib%TESTNUMBER - curl_multi_wait diff --git a/tests/data/test1501 b/tests/data/test1501 index 991cdc6fd1e5..2a47c2fdede1 100644 --- a/tests/data/test1501 +++ b/tests/data/test1501 @@ -26,9 +26,6 @@ DELAY TYPE 2 ftp - -lib%TESTNUMBER - ftp diff --git a/tests/data/test1502 b/tests/data/test1502 index b2ae4d984969..3508aeb43db0 100644 --- a/tests/data/test1502 +++ b/tests/data/test1502 @@ -32,9 +32,6 @@ Funny-head: yesyes http - -lib%TESTNUMBER - HTTP multi with CURLOPT_RESOLVE, cleanup sequence UA diff --git a/tests/data/test1506 b/tests/data/test1506 index 63c84f248be1..2aced6248ad2 100644 --- a/tests/data/test1506 +++ b/tests/data/test1506 @@ -53,9 +53,6 @@ http verbose-strings - -lib%TESTNUMBER - HTTP GET connection cache limit (CURLMOPT_MAXCONNECTS) diff --git a/tests/data/test1507 b/tests/data/test1507 index cebe1e776849..b684a1ef1e24 100644 --- a/tests/data/test1507 +++ b/tests/data/test1507 @@ -17,9 +17,6 @@ libtest smtp - -lib%TESTNUMBER - # based on bug report #1184 diff --git a/tests/data/test1508 b/tests/data/test1508 index 249a1b900ecd..afcd37201095 100644 --- a/tests/data/test1508 +++ b/tests/data/test1508 @@ -10,9 +10,6 @@ libtest # Client-side - -lib%TESTNUMBER - Close a multi handle without using it diff --git a/tests/data/test1509 b/tests/data/test1509 index 26697b3635df..158323b9b036 100644 --- a/tests/data/test1509 +++ b/tests/data/test1509 @@ -52,9 +52,6 @@ http http-proxy # tool is what to use instead of 'curl' - -lib%TESTNUMBER - proxy diff --git a/tests/data/test1510 b/tests/data/test1510 index 68a47e62b668..b6678c91927d 100644 --- a/tests/data/test1510 +++ b/tests/data/test1510 @@ -50,9 +50,6 @@ file contents should appear once for each file http - -lib%TESTNUMBER - HTTP GET connection cache limit (CURLOPT_MAXCONNECTS) diff --git a/tests/data/test1511 b/tests/data/test1511 index 0a2aa779b530..e4db6517c925 100644 --- a/tests/data/test1511 +++ b/tests/data/test1511 @@ -52,9 +52,6 @@ http http - -lib%TESTNUMBER - HTTP GET time conditions in repeated requests diff --git a/tests/data/test1512 b/tests/data/test1512 index 4cc606e9f8de..6b5292e09d31 100644 --- a/tests/data/test1512 +++ b/tests/data/test1512 @@ -49,9 +49,6 @@ file contents should appear once for each file http - -lib%TESTNUMBER - GLOBAL CACHE test over two easy performs diff --git a/tests/data/test1513 b/tests/data/test1513 index e02da3890f44..1d6f62b31804 100644 --- a/tests/data/test1513 +++ b/tests/data/test1513 @@ -24,9 +24,6 @@ wait 10 http - -lib%TESTNUMBER - return failure immediately from progress callback diff --git a/tests/data/test1514 b/tests/data/test1514 index 59ed526199e2..0bdc0f860df3 100644 --- a/tests/data/test1514 +++ b/tests/data/test1514 @@ -26,9 +26,6 @@ Connection: close http - -lib%TESTNUMBER - HTTP POST with read callback and unknown data size diff --git a/tests/data/test1515 b/tests/data/test1515 index b9f835566f98..e85f618d335e 100644 --- a/tests/data/test1515 +++ b/tests/data/test1515 @@ -43,9 +43,6 @@ hello http - -lib%TESTNUMBER - caching of manual libcurl DNS entries after DNS cache timeout diff --git a/tests/data/test1517 b/tests/data/test1517 index b853028a6e20..c2be9f859cd8 100644 --- a/tests/data/test1517 +++ b/tests/data/test1517 @@ -45,9 +45,6 @@ This is a virtual description of server virtual error. http # tool is what to use instead of 'curl' - -lib%TESTNUMBER - HTTP POST, server responds before completed send diff --git a/tests/data/test1518 b/tests/data/test1518 index 61a2dcb82878..10824d9ff7e8 100644 --- a/tests/data/test1518 +++ b/tests/data/test1518 @@ -28,9 +28,6 @@ Connection: close http # tool is what to use instead of 'curl' - -lib%TESTNUMBER - Extract Location: with broken URL diff --git a/tests/data/test1520 b/tests/data/test1520 index 527983460cf5..2383ac457987 100644 --- a/tests/data/test1520 +++ b/tests/data/test1520 @@ -13,9 +13,6 @@ libtest smtp - -lib%TESTNUMBER - SMTP with CRLF-dot-CRLF in upload payload diff --git a/tests/data/test1521 b/tests/data/test1521 index f7ab06ed0cf3..3e87ae60dad9 100644 --- a/tests/data/test1521 +++ b/tests/data/test1521 @@ -9,9 +9,6 @@ libtest # Client-side - -lib%TESTNUMBER - Test all curl_easy_setopt and curl_easy_getinfo options diff --git a/tests/data/test1522 b/tests/data/test1522 index d25659a1ff4e..d099dde58702 100644 --- a/tests/data/test1522 +++ b/tests/data/test1522 @@ -29,9 +29,6 @@ Funny-head: yesyes http - -lib%TESTNUMBER - CURLINFO_SIZE_UPLOAD with small SO_SNDBUF diff --git a/tests/data/test1523 b/tests/data/test1523 index 7668c8f20183..61c9e85fd95a 100644 --- a/tests/data/test1523 +++ b/tests/data/test1523 @@ -28,9 +28,6 @@ writedelay: 1000 http - -lib%TESTNUMBER - CURLINFO_LOW_SPEED_LIMIT diff --git a/tests/data/test1525 b/tests/data/test1525 index 61cdd74a1d75..9700386d127f 100644 --- a/tests/data/test1525 +++ b/tests/data/test1525 @@ -44,9 +44,6 @@ ETag: "21025-dc7-39462498" http http-proxy - -lib%TESTNUMBER - CURLOPT_PROXYHEADER is ignored CURLHEADER_UNIFIED diff --git a/tests/data/test1526 b/tests/data/test1526 index 459d3c318ffb..20309e213b62 100644 --- a/tests/data/test1526 +++ b/tests/data/test1526 @@ -46,9 +46,6 @@ ETag: "21025-dc7-39462498" http http-proxy - -lib%TESTNUMBER - CURLOPT_PROXYHEADER: separate host/proxy headers diff --git a/tests/data/test1527 b/tests/data/test1527 index 96b30c22574e..29f9c755d1e3 100644 --- a/tests/data/test1527 +++ b/tests/data/test1527 @@ -45,9 +45,6 @@ ETag: "21025-dc7-39462498" http http-proxy - -lib%TESTNUMBER - Same headers with CURLOPT_HEADEROPT == CURLHEADER_UNIFIED diff --git a/tests/data/test1528 b/tests/data/test1528 index 7e276c2f9e0f..422ba3925ac9 100644 --- a/tests/data/test1528 +++ b/tests/data/test1528 @@ -36,9 +36,6 @@ stop http http-proxy - -lib%TESTNUMBER - Separately specified proxy/server headers sent in a proxy GET diff --git a/tests/data/test1529 b/tests/data/test1529 index 3e14d16d38d0..24f4302d1ec3 100644 --- a/tests/data/test1529 +++ b/tests/data/test1529 @@ -24,9 +24,6 @@ We-are: good http http-proxy - -lib%TESTNUMBER - HTTP request-injection in URL sent over proxy diff --git a/tests/data/test1530 b/tests/data/test1530 index 8f23fd1822cf..dc2b480bcde2 100644 --- a/tests/data/test1530 +++ b/tests/data/test1530 @@ -11,9 +11,6 @@ libtest http - -lib%TESTNUMBER - CURLOPT_OPENSOCKETFUNCTION returns bad socket diff --git a/tests/data/test1531 b/tests/data/test1531 index bcf7fc192364..62675ea7f6fa 100644 --- a/tests/data/test1531 +++ b/tests/data/test1531 @@ -14,9 +14,6 @@ http http - -lib%TESTNUMBER - CURLOPT_POSTFIELDS with binary data set after multi_add_handle diff --git a/tests/data/test1532 b/tests/data/test1532 index 39a6e6d5f507..2ab148eae60a 100644 --- a/tests/data/test1532 +++ b/tests/data/test1532 @@ -23,9 +23,6 @@ Content-Length: 0 http # tool is what to use instead of 'curl' - -lib%TESTNUMBER - Test CURLINFO_RESPONSE_CODE diff --git a/tests/data/test1533 b/tests/data/test1533 index ed0093138c9d..9580fa84838c 100644 --- a/tests/data/test1533 +++ b/tests/data/test1533 @@ -28,9 +28,6 @@ Early Response http - -lib%TESTNUMBER - HTTP with CURLOPT_KEEP_SENDING_ON_ERROR and an early error response diff --git a/tests/data/test1534 b/tests/data/test1534 index 540072c01ada..0ab8b7052ded 100644 --- a/tests/data/test1534 +++ b/tests/data/test1534 @@ -25,9 +25,6 @@ Content-Length: 0 http # tool is what to use instead of 'curl' - -lib%TESTNUMBER - CURLINFO_FILETIME init and reset diff --git a/tests/data/test1535 b/tests/data/test1535 index b2ad1e8eff7a..fdf2b4e0efb9 100644 --- a/tests/data/test1535 +++ b/tests/data/test1535 @@ -24,9 +24,6 @@ Content-Length: 0 http # tool is what to use instead of 'curl' - -lib%TESTNUMBER - Test CURLINFO_RESPONSE_CODE CURLINFO_PROTOCOL diff --git a/tests/data/test1536 b/tests/data/test1536 index c350d7f620c9..2fb7ae49455f 100644 --- a/tests/data/test1536 +++ b/tests/data/test1536 @@ -24,9 +24,6 @@ Content-Length: 0 http # tool is what to use instead of 'curl' - -lib%TESTNUMBER - Test CURLINFO_RESPONSE_CODE CURLINFO_SCHEME diff --git a/tests/data/test1537 b/tests/data/test1537 index 2c4538e92ac2..8326b3401587 100644 --- a/tests/data/test1537 +++ b/tests/data/test1537 @@ -13,9 +13,6 @@ libtest # Client-side - -lib%TESTNUMBER - libcurl URL escape/unescape tests diff --git a/tests/data/test1538 b/tests/data/test1538 index c7347ae2973c..2c9dd8f0033a 100644 --- a/tests/data/test1538 +++ b/tests/data/test1538 @@ -17,9 +17,6 @@ libtest verbose-strings - -lib%TESTNUMBER - libcurl strerror API call tests diff --git a/tests/data/test1540 b/tests/data/test1540 index e3dff1c1f233..c23f75036e22 100644 --- a/tests/data/test1540 +++ b/tests/data/test1540 @@ -46,9 +46,6 @@ writedelay: 10 http - -lib%TESTNUMBER - chunked with trailers and pausing the receive diff --git a/tests/data/test1541 b/tests/data/test1541 index bbbdf7ab3973..b12e6e202917 100644 --- a/tests/data/test1541 +++ b/tests/data/test1541 @@ -53,9 +53,6 @@ writedelay: 10 http - -lib%TESTNUMBER - CURLINFO timer check diff --git a/tests/data/test1542 b/tests/data/test1542 index 6e08ae5507a3..640e90f8c0e8 100644 --- a/tests/data/test1542 +++ b/tests/data/test1542 @@ -29,9 +29,6 @@ http verbose-strings - -lib%TESTNUMBER - connection reuse with CURLOPT_MAXLIFETIME_CONN diff --git a/tests/data/test1545 b/tests/data/test1545 index a5e41f06e5b1..8fdfe54b6fac 100644 --- a/tests/data/test1545 +++ b/tests/data/test1545 @@ -20,9 +20,6 @@ form-api http # tool is what to use instead of 'curl' - -lib%TESTNUMBER - use curl_formadd() data twice with unreadable file diff --git a/tests/data/test1549 b/tests/data/test1549 index c198e8166a4c..44c102ffa7ec 100644 --- a/tests/data/test1549 +++ b/tests/data/test1549 @@ -33,9 +33,6 @@ cookies Receive expired cookies and CURLINFO_COOKIELIST - -lib%TESTNUMBER - http://%HOSTIP:%HTTPPORT/%TESTNUMBER diff --git a/tests/data/test1550 b/tests/data/test1550 index bdc7e6aa7462..78a27d144050 100644 --- a/tests/data/test1550 +++ b/tests/data/test1550 @@ -14,9 +14,6 @@ libtest # Client-side # tool is what to use instead of 'curl' - -lib%TESTNUMBER - verify setting pipelining blocklisting options diff --git a/tests/data/test1551 b/tests/data/test1551 index 259c2b6117f0..ce5524a27f34 100644 --- a/tests/data/test1551 +++ b/tests/data/test1551 @@ -36,9 +36,6 @@ redirected http # tool is what to use instead of 'curl' - -lib%TESTNUMBER - re-run redirected transfer without setting URL again diff --git a/tests/data/test1552 b/tests/data/test1552 index 312f9a1cfe69..b4b7a7bb4355 100644 --- a/tests/data/test1552 +++ b/tests/data/test1552 @@ -35,9 +35,6 @@ imap IMAP multi transfer error without curl_multi_remove_handle # tool is what to use instead of 'curl' - -lib%TESTNUMBER - 'imap://%HOSTIP:%IMAPPORT/%TESTNUMBER/;MAILINDEX=1' diff --git a/tests/data/test1553 b/tests/data/test1553 index 0757762bbe53..aca45aabb977 100644 --- a/tests/data/test1553 +++ b/tests/data/test1553 @@ -38,9 +38,6 @@ imap IMAP cleanup with non-existing hostname # tool is what to use instead of 'curl' - -lib%TESTNUMBER - # this MUST use a hostname that does not resolve diff --git a/tests/data/test1554 b/tests/data/test1554 index 054fdb59504d..93174472f32a 100644 --- a/tests/data/test1554 +++ b/tests/data/test1554 @@ -71,9 +71,6 @@ http HTTP with shared connection cache - -lib%TESTNUMBER - http://%HOSTIP:%HTTPPORT/%TESTNUMBER diff --git a/tests/data/test1555 b/tests/data/test1555 index 18e453d45dcf..c945c1ede50c 100644 --- a/tests/data/test1555 +++ b/tests/data/test1555 @@ -24,9 +24,6 @@ wait 10 http - -lib%TESTNUMBER - verify api is protected against calls from callbacks diff --git a/tests/data/test1556 b/tests/data/test1556 index 4447b4fdc3fe..6679243cde64 100644 --- a/tests/data/test1556 +++ b/tests/data/test1556 @@ -32,9 +32,6 @@ Long: %repeat[100001 x A]% http - -lib%TESTNUMBER - send long HTTP headers to header callback diff --git a/tests/data/test1557 b/tests/data/test1557 index d2db9ddfeaf4..ec61a50d00bf 100644 --- a/tests/data/test1557 +++ b/tests/data/test1557 @@ -12,9 +12,6 @@ libtest - -lib%TESTNUMBER - Remove easy handle in pending connections, no dangling entry diff --git a/tests/data/test1558 b/tests/data/test1558 index 43a3f672726f..07d552ffe810 100644 --- a/tests/data/test1558 +++ b/tests/data/test1558 @@ -15,9 +15,6 @@ libtest file - -lib%TESTNUMBER - CURLINFO_PROTOCOL for file:// transfer diff --git a/tests/data/test1559 b/tests/data/test1559 index b8f037c5e1a2..249ac7672836 100644 --- a/tests/data/test1559 +++ b/tests/data/test1559 @@ -18,9 +18,6 @@ libtest http verbose-strings - -lib%TESTNUMBER - Set excessive URL lengths diff --git a/tests/data/test1560 b/tests/data/test1560 index 8cbe742a35e3..30f3677a0397 100644 --- a/tests/data/test1560 +++ b/tests/data/test1560 @@ -26,9 +26,6 @@ ftp URL API - -lib%TESTNUMBER - diff --git a/tests/data/test1564 b/tests/data/test1564 index 838476d1f1b4..c5dd0466ec64 100644 --- a/tests/data/test1564 +++ b/tests/data/test1564 @@ -17,9 +17,6 @@ libtest wakeup - -lib%TESTNUMBER - wakeup before poll with no easy handles diff --git a/tests/data/test1565 b/tests/data/test1565 index e6b42c4a8a27..002ffeb09f3c 100644 --- a/tests/data/test1565 +++ b/tests/data/test1565 @@ -29,9 +29,6 @@ wakeup http - -lib%TESTNUMBER - wakeup from another thread diff --git a/tests/data/test1567 b/tests/data/test1567 index a017038ae6ca..9040528db1fd 100644 --- a/tests/data/test1567 +++ b/tests/data/test1567 @@ -37,9 +37,6 @@ redirected http # tool is what to use instead of 'curl' - -lib%TESTNUMBER - re-run redirected transfer without setting CURLU URL again diff --git a/tests/data/test1568 b/tests/data/test1568 index 780d6650ae5c..7102df0267a8 100644 --- a/tests/data/test1568 +++ b/tests/data/test1568 @@ -63,9 +63,6 @@ digest HTTP with Digest authorization on custom CURLOPT_PORT - -lib%TESTNUMBER - http://%HOSTIP/%TESTNUMBER %HTTPPORT diff --git a/tests/data/test1569 b/tests/data/test1569 index 850ff5984655..e77c2fed3990 100644 --- a/tests/data/test1569 +++ b/tests/data/test1569 @@ -31,9 +31,6 @@ ftp FTP first type=A then regular URL - -lib%TESTNUMBER - "ftp://%HOSTIP:%FTPPORT/%TESTNUMBER;type=A" ftp://%HOSTIP:%FTPPORT/%TESTNUMBER diff --git a/tests/data/test1571 b/tests/data/test1571 index 98fccbf0da4c..d86b5bb0b18d 100644 --- a/tests/data/test1571 +++ b/tests/data/test1571 @@ -62,9 +62,6 @@ http http - -lib%TESTNUMBER - CURLFOLLOW_OBEYCODE with custom POST method, 302 => GET diff --git a/tests/data/test1576 b/tests/data/test1576 index ae30f4ad22d5..a16ee6bcb884 100644 --- a/tests/data/test1576 +++ b/tests/data/test1576 @@ -62,9 +62,6 @@ http http - -lib%TESTNUMBER - CURLFOLLOW_OBEYCODE with custom PUT method, 302 => custom diff --git a/tests/data/test1582 b/tests/data/test1582 index bd32a750fabd..46f552dca336 100644 --- a/tests/data/test1582 +++ b/tests/data/test1582 @@ -35,9 +35,6 @@ https Negotiate over HTTPS with channel binding - -lib%TESTNUMBER - https://%HOSTIP:%HTTPSPORT/ diff --git a/tests/data/test1587 b/tests/data/test1587 index 9fcb77025f08..c00fdd16d667 100644 --- a/tests/data/test1587 +++ b/tests/data/test1587 @@ -34,9 +34,6 @@ https CURLINFO_TLS_SESSION and CURLINFO_TLS_SSL_PTR with OpenSSL - -lib%TESTNUMBER - https://%HOSTIP:%HTTPSPORT/ diff --git a/tests/data/test1588 b/tests/data/test1588 index 26ec3cf23a9f..c33f96d1cb58 100644 --- a/tests/data/test1588 +++ b/tests/data/test1588 @@ -60,9 +60,6 @@ Nice proxy auth sir! http # tool is what to use instead of 'curl' - -lib%TESTNUMBER - !SSPI crypto diff --git a/tests/data/test1589 b/tests/data/test1589 index 22dbfcd2fe7f..07ce45371bb8 100644 --- a/tests/data/test1589 +++ b/tests/data/test1589 @@ -59,9 +59,6 @@ http http-proxy # tool is what to use instead of 'curl' - -lib%TESTNUMBER - !SSPI crypto diff --git a/tests/data/test1591 b/tests/data/test1591 index a2c96109be89..91b1b7980e71 100644 --- a/tests/data/test1591 +++ b/tests/data/test1591 @@ -29,9 +29,6 @@ http HTTP PUT with trailers at the end - -lib%TESTNUMBER - http://%HOSTIP:%HTTPPORT/bzz/%TESTNUMBER diff --git a/tests/data/test1592 b/tests/data/test1592 index 8d7ff0737c5a..78553c70fdcc 100644 --- a/tests/data/test1592 +++ b/tests/data/test1592 @@ -14,9 +14,6 @@ libtest # Client-side - -lib%TESTNUMBER - HTTP request, remove handle while resolving, do not block diff --git a/tests/data/test1593 b/tests/data/test1593 index 5e8f130e3bb2..cc924d5bcb05 100644 --- a/tests/data/test1593 +++ b/tests/data/test1593 @@ -28,9 +28,6 @@ http HTTP custom header overrides CURLOPT_TIMECONDITION - -lib%TESTNUMBER - http://%HOSTIP:%HTTPPORT/%TESTNUMBER diff --git a/tests/data/test1594 b/tests/data/test1594 index 7cf1a27a96b1..d741a4e1b8ae 100644 --- a/tests/data/test1594 +++ b/tests/data/test1594 @@ -29,9 +29,6 @@ http HTTP Retry-After header parsing and extraction - -lib%TESTNUMBER - http://%HOSTIP:%HTTPPORT/%TESTNUMBER diff --git a/tests/data/test1597 b/tests/data/test1597 index cc3fd4cd2eac..7dcc7c1d2a9f 100644 --- a/tests/data/test1597 +++ b/tests/data/test1597 @@ -15,9 +15,6 @@ libtest CURLOPT_PROTOCOLS_STR - -lib%TESTNUMBER - diff --git a/tests/data/test1598 b/tests/data/test1598 index 58884291843d..e8626e252542 100644 --- a/tests/data/test1598 +++ b/tests/data/test1598 @@ -29,9 +29,6 @@ http HTTP POST with trailers at the end - -lib%TESTNUMBER - http://%HOSTIP:%HTTPPORT/bzz/%TESTNUMBER diff --git a/tests/data/test1599 b/tests/data/test1599 index b94c3d3c774c..d9e80d626b2a 100644 --- a/tests/data/test1599 +++ b/tests/data/test1599 @@ -27,9 +27,6 @@ FTP with netrc using no user but control code in password ftp://%HOSTIP:%FTPPORT/%TESTNUMBER %LOGDIR/netrc%TESTNUMBER - -lib%TESTNUMBER - default passwor?dlogin anonymou\ ' password login anonymous passwor?d.'macdef diff --git a/tests/data/test1605 b/tests/data/test1605 index 34e193c934e6..5aab454e7a70 100644 --- a/tests/data/test1605 +++ b/tests/data/test1605 @@ -9,9 +9,6 @@ libtest # Client-side - -lib%TESTNUMBER - Test negative data lengths as input to libcurl functions diff --git a/tests/data/test1616 b/tests/data/test1616 index 716ef132ad0d..7b3c76b44ee2 100644 --- a/tests/data/test1616 +++ b/tests/data/test1616 @@ -57,9 +57,6 @@ proxied one http - -lib%TESTNUMBER - !SSPI crypto diff --git a/tests/data/test1647 b/tests/data/test1647 index b49bc6129990..fe6411d8e5d6 100644 --- a/tests/data/test1647 +++ b/tests/data/test1647 @@ -50,9 +50,6 @@ https-proxy https # tool is what to use instead of 'curl' - -lib%TESTNUMBER - !SSPI crypto diff --git a/tests/data/test1648 b/tests/data/test1648 index 3be5f4951286..a8c7908c0da1 100644 --- a/tests/data/test1648 +++ b/tests/data/test1648 @@ -30,9 +30,6 @@ And you should ignore this data. http # tool is what to use instead of 'curl' - -lib%TESTNUMBER - proxy diff --git a/tests/data/test1649 b/tests/data/test1649 index b28908f8613b..2ec097782ab9 100644 --- a/tests/data/test1649 +++ b/tests/data/test1649 @@ -27,9 +27,6 @@ hello http - -lib%TESTNUMBER - Set referer first then NULL it diff --git a/tests/data/test1662 b/tests/data/test1662 index 4b8782ca65bb..4cfa2b940b31 100644 --- a/tests/data/test1662 +++ b/tests/data/test1662 @@ -46,9 +46,6 @@ http HTTP formpost from callback and a redirect and switch to GET - -lib%TESTNUMBER - http://%HOSTIP:%HTTPPORT/%TESTNUMBER diff --git a/tests/data/test1678 b/tests/data/test1678 index 72c0d4a81a1f..6aa869fd331e 100644 --- a/tests/data/test1678 +++ b/tests/data/test1678 @@ -12,9 +12,6 @@ libtest SSLS-EXPORT - -lib%TESTNUMBER - SSL session import tests diff --git a/tests/data/test1686 b/tests/data/test1686 index 4202c35b48b0..79ac376b5d2c 100644 --- a/tests/data/test1686 +++ b/tests/data/test1686 @@ -43,9 +43,6 @@ http HTTP Digest to different origins and switching credentials - -lib%TESTNUMBER - %HOSTIP %HTTPPORT diff --git a/tests/data/test1742 b/tests/data/test1742 index 823cc67ef627..fa440570856f 100644 --- a/tests/data/test1742 +++ b/tests/data/test1742 @@ -40,9 +40,6 @@ proxy http socks5 - -lib%TESTNUMBER - SOCKS5 CURLAUTH_ONLY does not offer the no authentication method diff --git a/tests/data/test1744 b/tests/data/test1744 index ff51ac89f90d..85fd6920e438 100644 --- a/tests/data/test1744 +++ b/tests/data/test1744 @@ -35,9 +35,6 @@ proxy http socks5 - -lib%TESTNUMBER - SOCKS5 CURLAUTH_ONLY does not reuse an unauthenticated connection diff --git a/tests/data/test1745 b/tests/data/test1745 index 957ae7bee1b2..d06aca443b5d 100644 --- a/tests/data/test1745 +++ b/tests/data/test1745 @@ -6,6 +6,7 @@ FTP PASV RETR SOCKS5 +libtest @@ -31,9 +32,6 @@ proxy ftp socks5 - -lib%TESTNUMBER - SOCKS5 CURLAUTH_ONLY ignores auth done for the FTP data socket diff --git a/tests/data/test1791 b/tests/data/test1791 index f035833b891b..dccafa70c1e6 100644 --- a/tests/data/test1791 +++ b/tests/data/test1791 @@ -18,9 +18,6 @@ libtest blank COPYPOSTFIELDS and duphandle - -lib%TESTNUMBER - localhost diff --git a/tests/data/test1900 b/tests/data/test1900 index 0c7d7984362b..f6597410edd7 100644 --- a/tests/data/test1900 +++ b/tests/data/test1900 @@ -22,9 +22,6 @@ http HSTS curl_easy_duphandle - -lib%TESTNUMBER - http://%HOSTIP:%NOLISTENPORT/not-there/%TESTNUMBER diff --git a/tests/data/test1901 b/tests/data/test1901 index 0fa2d0c7ab4d..4dfb330305a1 100644 --- a/tests/data/test1901 +++ b/tests/data/test1901 @@ -29,9 +29,6 @@ http Chunked HTTP POST from callback with CURLOPT_POSTFIELDSIZE set - -lib%TESTNUMBER - http://%HOSTIP:%HTTPPORT/boom diff --git a/tests/data/test1902 b/tests/data/test1902 index 5c826bd8a731..0dee174549f1 100644 --- a/tests/data/test1902 +++ b/tests/data/test1902 @@ -16,9 +16,6 @@ set COOKIEFILE and COOKIEJAR but make no transfer cookies - -lib%TESTNUMBER - %LOGDIR/cookie%TESTNUMBER diff --git a/tests/data/test1903 b/tests/data/test1903 index 54c94c10dc8a..205bd6ca0ef0 100644 --- a/tests/data/test1903 +++ b/tests/data/test1903 @@ -31,9 +31,6 @@ http CURLOPT_COOKIEFILE then reset then set again - -lib%TESTNUMBER - http://%HOSTIP:%HTTPPORT/we/want/%TESTNUMBER %LOGDIR/cookies%TESTNUMBER %LOGDIR/cookiesout%TESTNUMBER diff --git a/tests/data/test1905 b/tests/data/test1905 index 09685672c377..9ee2e5f65f94 100644 --- a/tests/data/test1905 +++ b/tests/data/test1905 @@ -31,9 +31,6 @@ http CURLOPT_COOKIELIST set to "FLUSH" of a shared cookie object - -lib%TESTNUMBER - http://%HOSTIP:%HTTPPORT/we/want/%TESTNUMBER %LOGDIR/cookies%TESTNUMBER diff --git a/tests/data/test1906 b/tests/data/test1906 index 0cac8b52e65c..dc30d80ef0ae 100644 --- a/tests/data/test1906 +++ b/tests/data/test1906 @@ -28,9 +28,6 @@ http CURLOPT_CURLU and CURLOPT_PORT - -lib%TESTNUMBER - # The tool does two requests, the first sets CURLOPT_PORT to 1 # the second resets the port again and expects that request to work. diff --git a/tests/data/test1907 b/tests/data/test1907 index 01c7ec2dd8f6..493fda6dae23 100644 --- a/tests/data/test1907 +++ b/tests/data/test1907 @@ -28,9 +28,6 @@ http CURLINFO_EFFECTIVE_URL with non-scheme URL - -lib%TESTNUMBER - %HOSTIP:%HTTPPORT/hello/../%TESTNUMBER diff --git a/tests/data/test1908 b/tests/data/test1908 index fb51bf20dde6..fc2c38ad3f10 100644 --- a/tests/data/test1908 +++ b/tests/data/test1908 @@ -48,9 +48,6 @@ alt-svc cache save after resetting the handle # make Debug-curl accept Alt-Svc over plain HTTP CURL_ALTSVC_HTTP="yeah" - -lib%TESTNUMBER - %HOSTIP:%HTTPPORT/%TESTNUMBER %LOGDIR/altsvc-%TESTNUMBER diff --git a/tests/data/test1910 b/tests/data/test1910 index 9994f3f33ca8..35dace54344a 100644 --- a/tests/data/test1910 +++ b/tests/data/test1910 @@ -40,9 +40,6 @@ http HTTP credentials with newline and redirect - -lib%TESTNUMBER - %HOSTIP:%HTTPPORT/%TESTNUMBER diff --git a/tests/data/test1911 b/tests/data/test1911 index 3b90faaed479..3fb994dc11ba 100644 --- a/tests/data/test1911 +++ b/tests/data/test1911 @@ -16,9 +16,6 @@ libtest verify that curl_easy_setopt() rejects too long string inputs - -lib%TESTNUMBER - diff --git a/tests/data/test1912 b/tests/data/test1912 index 326a3bc87719..39ee057a05ac 100644 --- a/tests/data/test1912 +++ b/tests/data/test1912 @@ -17,9 +17,6 @@ libtest Cross validate that gcc-typecheck macros match the option types. - -lib%TESTNUMBER - diff --git a/tests/data/test1913 b/tests/data/test1913 index ebab77dd9c0c..8bc4612bd128 100644 --- a/tests/data/test1913 +++ b/tests/data/test1913 @@ -25,9 +25,6 @@ ftp FTP with NOBODY set, getting a missing file - -lib%TESTNUMBER - ftp://%HOSTIP:%FTPPORT/not-there/%TESTNUMBER diff --git a/tests/data/test1915 b/tests/data/test1915 index 4d534deeb3c3..09880d1cc269 100644 --- a/tests/data/test1915 +++ b/tests/data/test1915 @@ -23,9 +23,6 @@ http HSTS read/write callbacks - -lib%TESTNUMBER - http://%HOSTIP:%NOLISTENPORT/not-there/%TESTNUMBER diff --git a/tests/data/test1916 b/tests/data/test1916 index 49ee85ae9844..93f38369d048 100644 --- a/tests/data/test1916 +++ b/tests/data/test1916 @@ -26,9 +26,6 @@ mqtt MQTT PUBLISH with no POSTFIELDSIZE set - -lib%TESTNUMBER - "mqtt://%HOSTIP:%MQTTPORT/%20" diff --git a/tests/data/test1918 b/tests/data/test1918 index 15d66219092d..bb238531b526 100644 --- a/tests/data/test1918 +++ b/tests/data/test1918 @@ -18,9 +18,6 @@ libtest curl_easy_option_by_name() and curl_easy_option_by_id() - -lib%TESTNUMBER - diff --git a/tests/data/test1919 b/tests/data/test1919 index 9829b25864a3..86b014820de6 100644 --- a/tests/data/test1919 +++ b/tests/data/test1919 @@ -27,9 +27,6 @@ http set CURLOPT_XOAUTH2_BEARER and do connection reuse - -lib%TESTNUMBER - http://%HOSTIP:%HTTPPORT/%TESTNUMBER diff --git a/tests/data/test1920 b/tests/data/test1920 index 901a5a3b5791..cd1cc7adf588 100644 --- a/tests/data/test1920 +++ b/tests/data/test1920 @@ -36,9 +36,6 @@ http cookies - -lib%TESTNUMBER - http://%HOSTIP:%HTTPPORT/ %LOGDIR/cookie%TESTNUMBER diff --git a/tests/data/test1921 b/tests/data/test1921 index 50018f6caa7a..4333b89dc89e 100644 --- a/tests/data/test1921 +++ b/tests/data/test1921 @@ -13,9 +13,6 @@ libtest Set a URL without scheme, then redirect with default scheme - -lib%TESTNUMBER - - diff --git a/tests/data/test1922 b/tests/data/test1922 index 64e5e803faf0..d6b7d020b632 100644 --- a/tests/data/test1922 +++ b/tests/data/test1922 @@ -52,9 +52,6 @@ CURL_HSTS_HTTP=yes curl_easy_duphandle copies HSTS cache, auto upgrading HTTP to HTTPS. - -lib%TESTNUMBER - - %HOSTIP %HTTPPORT %PROXYPORT diff --git a/tests/data/test1933 b/tests/data/test1933 index 0a9b4aa3d886..37821ce433fb 100644 --- a/tests/data/test1933 +++ b/tests/data/test1933 @@ -46,9 +46,6 @@ aws HTTP AWS_SIGV4 with one provider and auth cred via URL - -lib%TESTNUMBER - http://xxx:yyy@127.0.0.1:9000/%TESTNUMBER/testapi/test 127.0.0.1:9000:%HOSTIP:%HTTPPORT diff --git a/tests/data/test1934 b/tests/data/test1934 index bec4c2acff7b..89f6affd3035 100644 --- a/tests/data/test1934 +++ b/tests/data/test1934 @@ -46,9 +46,6 @@ aws HTTP AWS_SIGV4 with two providers - -lib%TESTNUMBER - http://127.0.0.1:9000/%TESTNUMBER/testapi/test 127.0.0.1:9000:%HOSTIP:%HTTPPORT diff --git a/tests/data/test1935 b/tests/data/test1935 index d7e092a04c2c..1542b0299261 100644 --- a/tests/data/test1935 +++ b/tests/data/test1935 @@ -46,9 +46,6 @@ aws HTTP AWS_SIGV4 with two providers and region - -lib%TESTNUMBER - http://127.0.0.1:9000/%TESTNUMBER/testapi/test 127.0.0.1:9000:%HOSTIP:%HTTPPORT diff --git a/tests/data/test1936 b/tests/data/test1936 index 2f634cff2449..d0dc0b493e39 100644 --- a/tests/data/test1936 +++ b/tests/data/test1936 @@ -46,9 +46,6 @@ aws HTTP AWS_SIGV4 with two providers, region and service - -lib%TESTNUMBER - http://127.0.0.1:9000/%TESTNUMBER/testapi/test 127.0.0.1:9000:%HOSTIP:%HTTPPORT diff --git a/tests/data/test1937 b/tests/data/test1937 index b5e5ab04dba8..cb34c897633e 100644 --- a/tests/data/test1937 +++ b/tests/data/test1937 @@ -47,9 +47,6 @@ aws HTTP POST with AWS_SIGV4 - -lib%TESTNUMBER - http://127.0.0.1:9000/%TESTNUMBER/testapi/test 127.0.0.1:9000:%HOSTIP:%HTTPPORT diff --git a/tests/data/test1938 b/tests/data/test1938 index 8e0c66403ff6..21978f2a1ae3 100644 --- a/tests/data/test1938 +++ b/tests/data/test1938 @@ -47,9 +47,6 @@ aws HTTP POST with AWS_SIGV4 for binary data - -lib%TESTNUMBER - http://127.0.0.1:9000/%TESTNUMBER/testapi/test 127.0.0.1:9000:%HOSTIP:%HTTPPORT diff --git a/tests/data/test1939 b/tests/data/test1939 index 248a3dc2fea9..ff945d270971 100644 --- a/tests/data/test1939 +++ b/tests/data/test1939 @@ -42,9 +42,6 @@ http-proxy curl_easy_cleanup without curl_multi_remove_handle - in CONNECT - -lib%TESTNUMBER - https://%HOSTIP:%HTTPPORT/%TESTNUMBER http://%HOSTIP:%PROXYPORT diff --git a/tests/data/test1940 b/tests/data/test1940 index 9a9443c5f99f..9544f372a4cc 100644 --- a/tests/data/test1940 +++ b/tests/data/test1940 @@ -43,9 +43,6 @@ http curl_easy_header - -lib%TESTNUMBER - http://%HOSTIP:%HTTPPORT/%TESTNUMBER diff --git a/tests/data/test1945 b/tests/data/test1945 index 0ed91e581861..4199e4d44d2e 100644 --- a/tests/data/test1945 +++ b/tests/data/test1945 @@ -46,9 +46,6 @@ http-proxy curl_easy_nextheader with server + CONNECT - -lib%TESTNUMBER - http://hello:%HTTPPORT/%TESTNUMBER %HOSTIP:%PROXYPORT diff --git a/tests/data/test1947 b/tests/data/test1947 index 27d150675637..1c834417359d 100644 --- a/tests/data/test1947 +++ b/tests/data/test1947 @@ -54,9 +54,6 @@ http curl_easy_nextheader on second request after first did redirects - -lib%TESTNUMBER - http://%HOSTIP:%HTTPPORT/%TESTNUMBER http://%HOSTIP:%HTTPPORT/%TESTNUMBER0003 diff --git a/tests/data/test1948 b/tests/data/test1948 index 907ce1dbacfa..badc295d25e6 100644 --- a/tests/data/test1948 +++ b/tests/data/test1948 @@ -44,9 +44,6 @@ http CURLOPT_POST after CURLOPT_UPLOAD reusing handle - -lib%TESTNUMBER - http://%HOSTIP:%HTTPPORT/%TESTNUMBER diff --git a/tests/data/test1955 b/tests/data/test1955 index 28251981f08b..2bbede0ea180 100644 --- a/tests/data/test1955 +++ b/tests/data/test1955 @@ -46,9 +46,6 @@ aws HTTP AWS_SIGV4 without X-Xxx-Content-Sha256 - -lib%TESTNUMBER - http://exam.ple.com:9000/aws_sigv4/testapi/test exam.ple.com:9000:%HOSTIP:%HTTPPORT diff --git a/tests/data/test1956 b/tests/data/test1956 index 161dea760fb4..df3dfa8b54c3 100644 --- a/tests/data/test1956 +++ b/tests/data/test1956 @@ -46,9 +46,6 @@ aws HTTP AWS_SIGV4 with X-Xxx-Content-Sha256 of empty payload - -lib%TESTNUMBER - http://exam.ple.com:9000/aws_sigv4/testapi/test exam.ple.com:9000:%HOSTIP:%HTTPPORT diff --git a/tests/data/test1957 b/tests/data/test1957 index 6cb1eab55129..22272501443b 100644 --- a/tests/data/test1957 +++ b/tests/data/test1957 @@ -46,9 +46,6 @@ aws HTTP AWS_SIGV4 with X-Xxx-Content-Sha256 of arbitrary payload - -lib%TESTNUMBER - http://exam.ple.com:9000/aws_sigv4/testapi/test exam.ple.com:9000:%HOSTIP:%HTTPPORT diff --git a/tests/data/test1958 b/tests/data/test1958 index 73713ddcae06..b40086caab79 100644 --- a/tests/data/test1958 +++ b/tests/data/test1958 @@ -45,9 +45,6 @@ aws HTTP AWS_SIGV4 with X-Xxx-Content-Sha256 with whitespace - -lib%TESTNUMBER - http://exam.ple.com:9000/aws_sigv4/testapi/test exam.ple.com:9000:%HOSTIP:%HTTPPORT diff --git a/tests/data/test1959 b/tests/data/test1959 index 3314fe7e4280..24798a2034b7 100644 --- a/tests/data/test1959 +++ b/tests/data/test1959 @@ -46,9 +46,6 @@ aws HTTP AWS_SIGV4 with long value for X-Xxx-Content-Sha256 - -lib%TESTNUMBER - http://exam.ple.com:9000/aws_sigv4/testapi/test exam.ple.com:9000:%HOSTIP:%HTTPPORT diff --git a/tests/data/test1960 b/tests/data/test1960 index d1a06282b070..f14f4ab375ff 100644 --- a/tests/data/test1960 +++ b/tests/data/test1960 @@ -33,9 +33,6 @@ http application hands over already connected socket - -lib%TESTNUMBER - http://%HOSTIP:%HTTPPORT/file %HOSTIP %HTTPPORT diff --git a/tests/data/test1964 b/tests/data/test1964 index ddf5f291740e..77fbaa27a703 100644 --- a/tests/data/test1964 +++ b/tests/data/test1964 @@ -44,9 +44,6 @@ aws HTTP AWS_SIGV4 with X-Xxx-Date header set manually - -lib%TESTNUMBER - http://xxx:yyy@127.0.0.1:9000/%TESTNUMBER/testapi/test 127.0.0.1:9000:%HOSTIP:%HTTPPORT diff --git a/tests/data/test1965 b/tests/data/test1965 index 645cb03fafb4..cffa490b1c03 100644 --- a/tests/data/test1965 +++ b/tests/data/test1965 @@ -12,9 +12,6 @@ libtest curl_url_set() different schemes - -lib%TESTNUMBER - # Verify data after the test has been "shot" diff --git a/tests/data/test1967 b/tests/data/test1967 index 18fb2c4a3c87..fb3f3e33793d 100644 --- a/tests/data/test1967 +++ b/tests/data/test1967 @@ -13,9 +13,6 @@ libtest curl_url_set() a URL without guessing a scheme - -lib%TESTNUMBER - http://%HOSTIP:%NOLISTENPORT/not-there/%TESTNUMBER diff --git a/tests/data/test1970 b/tests/data/test1970 index 42b10aa9c23f..c3c33e09fed0 100644 --- a/tests/data/test1970 +++ b/tests/data/test1970 @@ -46,9 +46,6 @@ aws HTTP AWS_SIGV4 for AWS S3: UPLOAD with INFILESIZE=0 - -lib%TESTNUMBER - http://exam.ple.com:9000/aws_sigv4/testapi/test exam.ple.com:9000:%HOSTIP:%HTTPPORT diff --git a/tests/data/test1971 b/tests/data/test1971 index d52343b9dde6..bfbec1ac5476 100644 --- a/tests/data/test1971 +++ b/tests/data/test1971 @@ -39,9 +39,6 @@ aws HTTP AWS_SIGV4 for AWS S3: UPLOAD with unknown INFILESIZE - -lib%TESTNUMBER - http://exam.ple.com:9000/aws_sigv4/testapi/test exam.ple.com:9000:%HOSTIP:%HTTPPORT diff --git a/tests/data/test1972 b/tests/data/test1972 index 451a6ec72aca..91cc8f1df741 100644 --- a/tests/data/test1972 +++ b/tests/data/test1972 @@ -49,9 +49,6 @@ CURL_ENTROPY=12345678 HTTP AWS_SIGV4 for AWS S3: MIMEPOST - -lib%TESTNUMBER - http://exam.ple.com:9000/aws_sigv4/testapi/test exam.ple.com:9000:%HOSTIP:%HTTPPORT diff --git a/tests/data/test1973 b/tests/data/test1973 index 31636f9f3f0b..3262b8b9617a 100644 --- a/tests/data/test1973 +++ b/tests/data/test1973 @@ -46,9 +46,6 @@ aws HTTP AWS_SIGV4 for AWS S3: POSTFIELDS - -lib%TESTNUMBER - http://exam.ple.com:9000/aws_sigv4/testapi/test exam.ple.com:9000:%HOSTIP:%HTTPPORT diff --git a/tests/data/test1974 b/tests/data/test1974 index 789da05df7d9..7d6e977ee6ca 100644 --- a/tests/data/test1974 +++ b/tests/data/test1974 @@ -46,9 +46,6 @@ aws HTTP AWS_SIGV4 for AWS S3: GET - -lib%TESTNUMBER - http://exam.ple.com:9000/aws_sigv4/testapi/test exam.ple.com:9000:%HOSTIP:%HTTPPORT diff --git a/tests/data/test1975 b/tests/data/test1975 index ac9312da60e7..151ad6bbe95d 100644 --- a/tests/data/test1975 +++ b/tests/data/test1975 @@ -39,9 +39,6 @@ aws HTTP AWS_SIGV4 for AWS S3: UPLOAD with given x-amz-content-sha256 - -lib%TESTNUMBER - http://exam.ple.com:9000/aws_sigv4/testapi/test exam.ple.com:9000:%HOSTIP:%HTTPPORT diff --git a/tests/data/test1977 b/tests/data/test1977 index f9b9dd820c32..5432aad4c1f7 100644 --- a/tests/data/test1977 +++ b/tests/data/test1977 @@ -28,9 +28,6 @@ http CURLOPT_CURLU and CURLINFO_EFFECTIVE_URL - -lib%TESTNUMBER - # The test does three transfers to check how CURLINFO_EFFECTIVE_URL is reported # when CURLOPT_CURLU changes between transfers. (Bug #15984) diff --git a/tests/data/test1978 b/tests/data/test1978 index 9e3d0f1586fd..9a5f1bfaa817 100644 --- a/tests/data/test1978 +++ b/tests/data/test1978 @@ -34,9 +34,6 @@ aws HTTP AWS_SIGV4 canonical request duplicate header test - -lib%TESTNUMBER - http://xxx:yyy@127.0.0.1:9000/%TESTNUMBER/testapi/test 127.0.0.1:9000:%HOSTIP:%HTTPPORT diff --git a/tests/data/test1985 b/tests/data/test1985 index 01e3d0564b5a..ba382777d316 100644 --- a/tests/data/test1985 +++ b/tests/data/test1985 @@ -42,9 +42,6 @@ Digest response when not asked, then use Digest crypto digest - -lib%TESTNUMBER - http://firsthost:%HTTPPORT/%TESTNUMBER http://secondhost:%HTTPPORT/%TESTNUMBER0001 %HTTPPORT %HOSTIP diff --git a/tests/data/test2023 b/tests/data/test2023 index 062812bc6930..c759a2433094 100644 --- a/tests/data/test2023 +++ b/tests/data/test2023 @@ -107,9 +107,6 @@ Finally, this is the real page! http - -lib%TESTNUMBER - HTTP authorization retry (Basic) diff --git a/tests/data/test2032 b/tests/data/test2032 index b5fa45a4ab41..7a2e753ebbcc 100644 --- a/tests/data/test2032 +++ b/tests/data/test2032 @@ -76,9 +76,6 @@ SSL http - -lib%TESTNUMBER - NTLM connection mapping diff --git a/tests/data/test2082 b/tests/data/test2082 index 2ec215ddfe2f..789f3e93afe8 100644 --- a/tests/data/test2082 +++ b/tests/data/test2082 @@ -28,9 +28,6 @@ http Pre-request callback for HTTP - -lib%TESTNUMBER - %HOSTIP:%HTTPPORT/%TESTNUMBER diff --git a/tests/data/test2118 b/tests/data/test2118 index 7e795e535f30..9b2eff8846c0 100644 --- a/tests/data/test2118 +++ b/tests/data/test2118 @@ -24,9 +24,6 @@ local-http https test-localhost.nn.pem - -lib%TESTNUMBER - VERIFYHOST with VERIFYPEER disabled rejects a mismatching certificate diff --git a/tests/data/test2270 b/tests/data/test2270 index 5fd59a100b07..a5628d91e42a 100644 --- a/tests/data/test2270 +++ b/tests/data/test2270 @@ -27,9 +27,6 @@ form-api http - -lib%TESTNUMBER - reject HTTP form and MIME upload resume diff --git a/tests/data/test2301 b/tests/data/test2301 index c6f120f226ec..0a31d54e1df0 100644 --- a/tests/data/test2301 +++ b/tests/data/test2301 @@ -41,9 +41,6 @@ http WebSockets via callback (raw mode) + curl_ws_send() - -lib%TESTNUMBER - ws://%HOSTIP:%HTTPPORT/%TESTNUMBER diff --git a/tests/data/test2302 b/tests/data/test2302 index 02df3884686b..9a158680c08a 100644 --- a/tests/data/test2302 +++ b/tests/data/test2302 @@ -41,9 +41,6 @@ http WebSockets via callback (frame mode) + curl_ws_send() - -lib%TESTNUMBER - ws://%HOSTIP:%HTTPPORT/%TESTNUMBER diff --git a/tests/data/test2304 b/tests/data/test2304 index 19ca6547af01..7ba4ba5ebfc0 100644 --- a/tests/data/test2304 +++ b/tests/data/test2304 @@ -40,9 +40,6 @@ http WebSockets curl_ws_recv() with closed connection - -lib%TESTNUMBER - ws://%HOSTIP:%HTTPPORT/%TESTNUMBER diff --git a/tests/data/test2306 b/tests/data/test2306 index eec63c1ae5c4..c46b0346f47e 100644 --- a/tests/data/test2306 +++ b/tests/data/test2306 @@ -40,9 +40,6 @@ Date: Wed, 10 May 2023 14:58:08 GMT http # tool to run - -lib%TESTNUMBER - HTTP GET reused handle with first header folded diff --git a/tests/data/test2308 b/tests/data/test2308 index 68677e041416..bc015325c0cd 100644 --- a/tests/data/test2308 +++ b/tests/data/test2308 @@ -30,9 +30,6 @@ Funny-head: yesyes http # tool to run - -lib%TESTNUMBER - verify return code when write callback returns error diff --git a/tests/data/test2309 b/tests/data/test2309 index 6e5dbd3e1d00..c0a27375aeab 100644 --- a/tests/data/test2309 +++ b/tests/data/test2309 @@ -38,9 +38,6 @@ proxy HTTP with .netrc using duped easy handle - -lib%TESTNUMBER - http://github.com %LOGDIR/netrc%TESTNUMBER http://%HOSTIP:%HTTPPORT/ diff --git a/tests/data/test2397 b/tests/data/test2397 index 444e804595e0..42e2acfa9a97 100644 --- a/tests/data/test2397 +++ b/tests/data/test2397 @@ -28,10 +28,6 @@ hej http - -lib%TESTNUMBER - - Get CURLINFO_REFERER set CURLOPT_REFERER diff --git a/tests/data/test2402 b/tests/data/test2402 index 7a2ec6f4a580..d1f89419b5ea 100644 --- a/tests/data/test2402 +++ b/tests/data/test2402 @@ -56,9 +56,6 @@ verbose-strings http/2 - -lib%TESTNUMBER - HTTP GET multiple files over HTTP/2 using HTTPS diff --git a/tests/data/test2404 b/tests/data/test2404 index 5a6b6a41fc4c..010ac79bf359 100644 --- a/tests/data/test2404 +++ b/tests/data/test2404 @@ -56,9 +56,6 @@ verbose-strings http/2 - -lib%TESTNUMBER - HTTP/2 using STREAM_WEIGHTs diff --git a/tests/data/test2405 b/tests/data/test2405 index 06f659a3f687..f394c48ed566 100644 --- a/tests/data/test2405 +++ b/tests/data/test2405 @@ -35,9 +35,6 @@ wakeup http - -lib%TESTNUMBER - checking curl_multi_waitfds functionality diff --git a/tests/data/test2412 b/tests/data/test2412 index 9d67a282a952..b26811dddec4 100644 --- a/tests/data/test2412 +++ b/tests/data/test2412 @@ -34,9 +34,6 @@ wakeup http - -lib%TESTNUMBER - checking curl_multi_fdset on nothing to do diff --git a/tests/data/test2414 b/tests/data/test2414 index 7c227cab27a3..ce72d494af28 100644 --- a/tests/data/test2414 +++ b/tests/data/test2414 @@ -15,9 +15,6 @@ wakeup - -lib%TESTNUMBER - wakeup, perform and poll needs to exit diff --git a/tests/data/test2453 b/tests/data/test2453 index 6fc757a06d37..e8cca5adeed0 100644 --- a/tests/data/test2453 +++ b/tests/data/test2453 @@ -33,9 +33,6 @@ http CURLMINFO_XFERS_DONE tracks unread completion messages - -lib%TESTNUMBER - http://%HOSTIP:%HTTPPORT/%TESTNUMBER diff --git a/tests/data/test2502 b/tests/data/test2502 index 2cf16a2d2bb7..a41483585db1 100644 --- a/tests/data/test2502 +++ b/tests/data/test2502 @@ -55,9 +55,6 @@ verbose-strings http/3 - -lib%TESTNUMBER - HTTP GET multiple over HTTP/3 diff --git a/tests/data/test2504 b/tests/data/test2504 index 97f254081502..4af2cf0eff09 100644 --- a/tests/data/test2504 +++ b/tests/data/test2504 @@ -26,9 +26,6 @@ file contents should appear once for each file http - -lib%TESTNUMBER - custom Host with cookie, handle reuse, no custom Host: diff --git a/tests/data/test2505 b/tests/data/test2505 index 217b3033e224..111e10232ed0 100644 --- a/tests/data/test2505 +++ b/tests/data/test2505 @@ -36,9 +36,6 @@ file contents should appear once for each file http - -lib%TESTNUMBER - verify CURLOPT_AUTOREFERER switched off diff --git a/tests/data/test2506 b/tests/data/test2506 index 7e79bcc5a63e..6f051331cc9b 100644 --- a/tests/data/test2506 +++ b/tests/data/test2506 @@ -33,9 +33,6 @@ http proxy - -lib%TESTNUMBER - netrc with redirect using proxy diff --git a/tests/data/test2700 b/tests/data/test2700 index 8a875b682be6..2c558911366d 100644 --- a/tests/data/test2700 +++ b/tests/data/test2700 @@ -18,9 +18,6 @@ ws http - -lib%TESTNUMBER - ws://%HOSTIP:%HTTPPORT/%TESTNUMBER diff --git a/tests/data/test2805 b/tests/data/test2805 index 9f151500f2e1..20c17f63fe4a 100644 --- a/tests/data/test2805 +++ b/tests/data/test2805 @@ -17,9 +17,6 @@ libtest ftp - -lib%TESTNUMBER - FTP wildcards with percent sign in file name diff --git a/tests/data/test2924 b/tests/data/test2924 index 7a852360f1f3..283043f4f0a0 100644 --- a/tests/data/test2924 +++ b/tests/data/test2924 @@ -28,9 +28,6 @@ chunked data fun%CR # Client-side - -lib%TESTNUMBER - http diff --git a/tests/data/test3010 b/tests/data/test3010 index 770ca7d49204..8da249ba87e1 100644 --- a/tests/data/test3010 +++ b/tests/data/test3010 @@ -40,9 +40,6 @@ http HTTP retry-after reset - -lib%TESTNUMBER - %HOSTIP:%HTTPPORT/%TESTNUMBER diff --git a/tests/data/test3025 b/tests/data/test3025 index 37c07562825a..4440f2dd50f5 100644 --- a/tests/data/test3025 +++ b/tests/data/test3025 @@ -26,9 +26,6 @@ http CURLOPT_HTTP200ALIASES - -lib%TESTNUMBER - http://%HOSTIP:%HTTPPORT/%TESTNUMBER diff --git a/tests/data/test3026 b/tests/data/test3026 index 6987634a492f..933a0fcd0fb5 100644 --- a/tests/data/test3026 +++ b/tests/data/test3026 @@ -24,9 +24,6 @@ threaded-resolver curl_global_init thread-safety - -lib%TESTNUMBER - # Verify data after the test has been "shot" diff --git a/tests/data/test3027 b/tests/data/test3027 index deb9e9c1d224..49bd0dc012fb 100644 --- a/tests/data/test3027 +++ b/tests/data/test3027 @@ -29,9 +29,6 @@ ftp Get a file via FTP but 550 after MDTM command - -lib%TESTNUMBER - ftp://%HOSTIP:%FTPPORT/%TESTNUMBER diff --git a/tests/data/test3033 b/tests/data/test3033 index d633b218e5c2..a4f7ed7dd320 100644 --- a/tests/data/test3033 +++ b/tests/data/test3033 @@ -34,9 +34,6 @@ http CURLOPT_FRESH_CONNECT=2 - -lib%TESTNUMBER - http://%HOSTIP:%HTTPPORT/%TESTNUMBER diff --git a/tests/data/test3034 b/tests/data/test3034 index cfd1c5f447c6..92f5682fd44e 100644 --- a/tests/data/test3034 +++ b/tests/data/test3034 @@ -23,9 +23,6 @@ Location: /%TESTNUMBER http - -lib%TESTNUMBER - Test reset resolves rewind failure diff --git a/tests/data/test3100 b/tests/data/test3100 index 4aaaace52bf2..0a4b19724a8a 100644 --- a/tests/data/test3100 +++ b/tests/data/test3100 @@ -55,9 +55,6 @@ u=http://www.curl.example.com/fakesdp.ps rtsp - -lib%TESTNUMBER - RTSP Authentication check diff --git a/tests/data/test3101 b/tests/data/test3101 index 91ee83219d93..7d3afb3af459 100644 --- a/tests/data/test3101 +++ b/tests/data/test3101 @@ -60,9 +60,6 @@ https http - -lib%TESTNUMBER - HTTP auth without redirection protocols diff --git a/tests/data/test3102 b/tests/data/test3102 index aa4cda8feb8c..06876162f060 100644 --- a/tests/data/test3102 +++ b/tests/data/test3102 @@ -24,9 +24,6 @@ SSL https - -lib%TESTNUMBER - verify certificate chain order with simple HTTPS GET diff --git a/tests/data/test3103 b/tests/data/test3103 index 681f0215b950..884b9f1fca44 100644 --- a/tests/data/test3103 +++ b/tests/data/test3103 @@ -34,9 +34,6 @@ proxy http - -lib%TESTNUMBER - CURLOPT_COOKIELIST without expiry diff --git a/tests/data/test3104 b/tests/data/test3104 index 0ac708af87df..dafa13b66c71 100644 --- a/tests/data/test3104 +++ b/tests/data/test3104 @@ -34,9 +34,6 @@ proxy http - -lib%TESTNUMBER - CURLOPT_COOKIELIST with Netscape format diff --git a/tests/data/test3105 b/tests/data/test3105 index 5198e04a9c1c..898ae185fc60 100644 --- a/tests/data/test3105 +++ b/tests/data/test3105 @@ -9,9 +9,6 @@ libtest # Client-side - -lib%TESTNUMBER - curl_multi_remove_handle twice diff --git a/tests/data/test3107 b/tests/data/test3107 index 4b0e75900a87..732efe68647a 100644 --- a/tests/data/test3107 +++ b/tests/data/test3107 @@ -25,9 +25,6 @@ hello http - -lib%TESTNUMBER - reject internal easy handle passed to a notify callback diff --git a/tests/data/test3207 b/tests/data/test3207 index 9334527640ed..303caae11727 100644 --- a/tests/data/test3207 +++ b/tests/data/test3207 @@ -35,9 +35,6 @@ https concurrent HTTPS GET using shared SSL session cache - -lib%TESTNUMBER - # provide URL and ca-cert https://localhost:%HTTPSPORT/%TESTNUMBER %CERTDIR/certs/test-ca.crt diff --git a/tests/data/test3208 b/tests/data/test3208 index fb2936fb96e8..d1c991c016f9 100644 --- a/tests/data/test3208 +++ b/tests/data/test3208 @@ -31,9 +31,6 @@ Funny-head: yesyes https # tool is what to use instead of 'curl' - -lib%TESTNUMBER - easy_perform, multi_perform, easy_perform the same handle diff --git a/tests/data/test3231 b/tests/data/test3231 index 08038902a964..1966e508fab1 100644 --- a/tests/data/test3231 +++ b/tests/data/test3231 @@ -28,9 +28,6 @@ http CONNECT_ONLY survives the per-host connection limit - -lib%TESTNUMBER - http://%HOSTIP:%HTTPPORT/%TESTNUMBER diff --git a/tests/data/test3233 b/tests/data/test3233 index 0bb32dc02f54..c53ef9c45159 100644 --- a/tests/data/test3233 +++ b/tests/data/test3233 @@ -34,9 +34,6 @@ Curl-private: swsclose rtsp - -lib%TESTNUMBER - RTSP session id is not sent to another origin after a redirect diff --git a/tests/data/test3595 b/tests/data/test3595 index f740e61002b0..ff139be02413 100644 --- a/tests/data/test3595 +++ b/tests/data/test3595 @@ -9,9 +9,6 @@ libtest # Client-side - -lib%TESTNUMBER - curl_mime_free diff --git a/tests/data/test3596 b/tests/data/test3596 index a93722de2bf0..d123963007a3 100644 --- a/tests/data/test3596 +++ b/tests/data/test3596 @@ -21,9 +21,6 @@ Mime http - -lib%TESTNUMBER - Curl_mime_prepare_headers diff --git a/tests/data/test464 b/tests/data/test464 index 3ef4e463b615..b0974b92ce01 100644 --- a/tests/data/test464 +++ b/tests/data/test464 @@ -14,9 +14,6 @@ libtest Debug asyn-rr - -lib%TESTNUMBER - https://localhost:%NOLISTENPORT/464 diff --git a/tests/data/test500 b/tests/data/test500 index d1459f095529..b36f7b254157 100644 --- a/tests/data/test500 +++ b/tests/data/test500 @@ -31,9 +31,6 @@ Funny-head: yesyes http # tool is what to use instead of 'curl' - -lib%TESTNUMBER - simple libcurl HTTP GET tool diff --git a/tests/data/test5000 b/tests/data/test5000 index a35212711ad5..1a3f99edec0d 100644 --- a/tests/data/test5000 +++ b/tests/data/test5000 @@ -35,9 +35,6 @@ httpsig httpsig: basic GET with Ed25519 - -lib%TESTNUMBER - http://example.com:9000/%TESTNUMBER/resource example.com:9000:%HOSTIP:%HTTPPORT diff --git a/tests/data/test5004 b/tests/data/test5004 index d0dc60970142..bd6e83e8f213 100644 --- a/tests/data/test5004 +++ b/tests/data/test5004 @@ -36,9 +36,6 @@ httpsig httpsig: Ed25519 POST with headers (RFC test vector) - -lib%TESTNUMBER - CURL_HTTPSIG_CREATED=1618884473 diff --git a/tests/data/test501 b/tests/data/test501 index cfa1fe379fee..3749d9bcf171 100644 --- a/tests/data/test501 +++ b/tests/data/test501 @@ -16,9 +16,6 @@ libtest http # tool is what to use instead of 'curl' - -lib%TESTNUMBER - simple libcurl attempt operation without URL set diff --git a/tests/data/test502 b/tests/data/test502 index de993097b94f..fde002d3597c 100644 --- a/tests/data/test502 +++ b/tests/data/test502 @@ -23,9 +23,6 @@ moo file # tool is what to use instead of 'curl' - -lib%TESTNUMBER - simple multi file:// get diff --git a/tests/data/test503 b/tests/data/test503 index 8c735edb4fe8..a2748167eda9 100644 --- a/tests/data/test503 +++ b/tests/data/test503 @@ -49,9 +49,6 @@ http http-proxy # tool is what to use instead of 'curl' - -lib%TESTNUMBER - proxy diff --git a/tests/data/test504 b/tests/data/test504 index d70a576658e1..87aed4566ada 100644 --- a/tests/data/test504 +++ b/tests/data/test504 @@ -22,9 +22,6 @@ http proxy # tool is what to use instead of 'curl' - -lib%TESTNUMBER - simple multi through local proxy without listener diff --git a/tests/data/test505 b/tests/data/test505 index e99ed9f021e7..c16ca7ecb639 100644 --- a/tests/data/test505 +++ b/tests/data/test505 @@ -18,9 +18,6 @@ libtest ftp # tool is what to use instead of 'curl' - -lib%TESTNUMBER - FTP upload with rename after transfer diff --git a/tests/data/test506 b/tests/data/test506 index addd21e5a8c9..9dbc32d53433 100644 --- a/tests/data/test506 +++ b/tests/data/test506 @@ -72,9 +72,6 @@ HTTP with shared cookie list (and dns cache) TZ=GMT - -lib%TESTNUMBER - http://%HOSTIP:%HTTPPORT/%TESTNUMBER %LOGDIR/jar%TESTNUMBER diff --git a/tests/data/test507 b/tests/data/test507 index da618f87292a..c527785a8932 100644 --- a/tests/data/test507 +++ b/tests/data/test507 @@ -21,9 +21,6 @@ http multi interface get with non-existing hostname - -lib%TESTNUMBER - http://non-existing-host.haxx.se/ diff --git a/tests/data/test508 b/tests/data/test508 index cf48dfc0d036..f99a431b60f0 100644 --- a/tests/data/test508 +++ b/tests/data/test508 @@ -27,9 +27,6 @@ hello http # tool is what to use instead of 'curl' - -lib%TESTNUMBER - send HTTP POST using read callback diff --git a/tests/data/test509 b/tests/data/test509 index dc2032624f0f..6092284dd1e0 100644 --- a/tests/data/test509 +++ b/tests/data/test509 @@ -16,9 +16,6 @@ libtest http # tool is what to use instead of 'curl' - -lib%TESTNUMBER - initialization with memory callbacks and actual usage diff --git a/tests/data/test510 b/tests/data/test510 index f8c04388acad..730149f61f81 100644 --- a/tests/data/test510 +++ b/tests/data/test510 @@ -28,9 +28,6 @@ hello http # tool is what to use instead of 'curl' - -lib%TESTNUMBER - send HTTP POST using read callback, using chunked transfer-encoding diff --git a/tests/data/test511 b/tests/data/test511 index 9687778a499b..fb4675d82835 100644 --- a/tests/data/test511 +++ b/tests/data/test511 @@ -21,9 +21,6 @@ REPLY SIZE 550 bluah you f00l! ftp # tool is what to use instead of 'curl' - -lib%TESTNUMBER - FTP with FILETIME and NOBODY but missing file diff --git a/tests/data/test512 b/tests/data/test512 index 6947604eca47..58e673b0dec8 100644 --- a/tests/data/test512 +++ b/tests/data/test512 @@ -26,9 +26,6 @@ hello http # tool is what to use instead of 'curl' - -lib%TESTNUMBER - simple curl_easy_duplicate() test diff --git a/tests/data/test513 b/tests/data/test513 index 18f2fb361a95..d1ddc519326f 100644 --- a/tests/data/test513 +++ b/tests/data/test513 @@ -18,9 +18,6 @@ libtest http # tool is what to use instead of 'curl' - -lib%TESTNUMBER - send HTTP POST using read callback that returns CURL_READFUNC_ABORT diff --git a/tests/data/test514 b/tests/data/test514 index cf4c8db34dc6..a09079744c14 100644 --- a/tests/data/test514 +++ b/tests/data/test514 @@ -29,9 +29,6 @@ Funny-head: yesyes http - -lib%TESTNUMBER - First set options to POST and then to make HEAD diff --git a/tests/data/test515 b/tests/data/test515 index 261503720b0f..1f5cac33fa81 100644 --- a/tests/data/test515 +++ b/tests/data/test515 @@ -25,9 +25,6 @@ OK http # tool is what to use instead of 'curl' - -lib%TESTNUMBER - make a POSTFIELDS set to NULL with POSTFIELDSIZE set to zero diff --git a/tests/data/test516 b/tests/data/test516 index d64d6915c275..5518c6452bbc 100644 --- a/tests/data/test516 +++ b/tests/data/test516 @@ -30,9 +30,6 @@ form-api http # tool is what to use instead of 'curl' - -lib%TESTNUMBER - make an HTTPPOST set to NULL diff --git a/tests/data/test517 b/tests/data/test517 index c90659e5eedb..e67f851d5296 100644 --- a/tests/data/test517 +++ b/tests/data/test517 @@ -13,9 +13,6 @@ libtest # Client-side # tool is what to use instead of 'curl' - -lib%TESTNUMBER - curl_getdate() testing diff --git a/tests/data/test518 b/tests/data/test518 index 946ee01575f2..0b2e8ba6779b 100644 --- a/tests/data/test518 +++ b/tests/data/test518 @@ -35,9 +35,6 @@ http getrlimit # tool is what to use instead of 'curl' - -lib%TESTNUMBER - # precheck is a command line to run before the test, to see if we can execute # the test or not diff --git a/tests/data/test519 b/tests/data/test519 index 36a803bf6535..73e474d9bae0 100644 --- a/tests/data/test519 +++ b/tests/data/test519 @@ -48,9 +48,6 @@ content2 http # tool is what to use instead of 'curl' - -lib%TESTNUMBER - GET same URL twice with different users diff --git a/tests/data/test520 b/tests/data/test520 index d0d34e8056bf..1ad68b70ce86 100644 --- a/tests/data/test520 +++ b/tests/data/test520 @@ -23,9 +23,6 @@ REPLY MDTM 213 20030405060708 ftp # tool is what to use instead of 'curl' - -lib%TESTNUMBER - FTP RETR with FILETIME diff --git a/tests/data/test521 b/tests/data/test521 index fef287c84563..be4c69bfcf9b 100644 --- a/tests/data/test521 +++ b/tests/data/test521 @@ -30,9 +30,6 @@ dr-xr-xr-x 5 0 1 512 Oct 1 1997 usr ftp - -lib%TESTNUMBER - FTP dir list PASV with CURLOPT_PORT diff --git a/tests/data/test523 b/tests/data/test523 index 928e3afc4d1d..f98643f680d1 100644 --- a/tests/data/test523 +++ b/tests/data/test523 @@ -31,9 +31,6 @@ hello http - -lib%TESTNUMBER - HTTP GET with proxy and CURLOPT_PORT diff --git a/tests/data/test524 b/tests/data/test524 index 6b946dfba277..ac45ee3ea7db 100644 --- a/tests/data/test524 +++ b/tests/data/test524 @@ -17,9 +17,6 @@ libtest ftp - -lib%TESTNUMBER - FTP upload with target URL ending with slash diff --git a/tests/data/test525 b/tests/data/test525 index 7c6f152ea902..e86a6113aba8 100644 --- a/tests/data/test525 +++ b/tests/data/test525 @@ -21,9 +21,6 @@ libtest ftp - -lib%TESTNUMBER - FTP PORT upload using multi interface diff --git a/tests/data/test526 b/tests/data/test526 index daaf2e4ff728..2bb05dac92b5 100644 --- a/tests/data/test526 +++ b/tests/data/test526 @@ -28,9 +28,6 @@ file contents should appear once for each file ftp - -lib%TESTNUMBER - FTP RETR same file using different handles but same connection diff --git a/tests/data/test530 b/tests/data/test530 index feb96b9a27e8..17b9803c3ce9 100644 --- a/tests/data/test530 +++ b/tests/data/test530 @@ -32,9 +32,6 @@ Funny-head: yesyes http - -lib%TESTNUMBER - multi_socket interface transfer with callbacks returning error diff --git a/tests/data/test533 b/tests/data/test533 index 1318d4583bd7..c88ccd3b55fd 100644 --- a/tests/data/test533 +++ b/tests/data/test533 @@ -26,9 +26,6 @@ REPLY SIZE 500 Can't check for file existence ftp - -lib%TESTNUMBER - FTP RETR a non-existing file twice using the multi interface diff --git a/tests/data/test536 b/tests/data/test536 index 8d7f0510cf9e..e0af3ef61ac2 100644 --- a/tests/data/test536 +++ b/tests/data/test536 @@ -32,9 +32,6 @@ hello http - -lib%TESTNUMBER - CURLINFO_USED_PROXY diff --git a/tests/data/test537 b/tests/data/test537 index e365afd69a84..7be43454b162 100644 --- a/tests/data/test537 +++ b/tests/data/test537 @@ -35,9 +35,6 @@ http getrlimit # tool is what to use instead of 'curl' - -lib%TESTNUMBER - # precheck is a command line to run before the test, to see if we can execute # the test or not diff --git a/tests/data/test539 b/tests/data/test539 index 144a127380cc..d13456e838fc 100644 --- a/tests/data/test539 +++ b/tests/data/test539 @@ -29,9 +29,6 @@ contents for file number two ftp # tool is what to use instead of 'curl' - -lib%TESTNUMBER - Two FTP fetches using different CURLOPT_FTP_FILEMETHOD diff --git a/tests/data/test540 b/tests/data/test540 index 8842d9ff6f96..4a75376004c1 100644 --- a/tests/data/test540 +++ b/tests/data/test540 @@ -63,9 +63,6 @@ Nice proxy auth sir! http # tool is what to use instead of 'curl' - -lib%TESTNUMBER - !SSPI crypto diff --git a/tests/data/test541 b/tests/data/test541 index 324604206072..68a0dc29eeb9 100644 --- a/tests/data/test541 +++ b/tests/data/test541 @@ -18,9 +18,6 @@ libtest ftp # tool is what to use instead of 'curl' - -lib%TESTNUMBER - FTP upload and upload same file again without rewind diff --git a/tests/data/test542 b/tests/data/test542 index 494f239b3915..bc57ec77c04b 100644 --- a/tests/data/test542 +++ b/tests/data/test542 @@ -29,9 +29,6 @@ Accept-ranges: bytes ftp - -lib%TESTNUMBER - FTP a file with NOBODY yes and HEADER no diff --git a/tests/data/test543 b/tests/data/test543 index d40475238542..02aadd3d72ac 100644 --- a/tests/data/test543 +++ b/tests/data/test543 @@ -10,9 +10,6 @@ libtest # Client-side - -lib%TESTNUMBER - curl_easy_escape diff --git a/tests/data/test544 b/tests/data/test544 index fd798746c356..5b6aeb0a2e13 100644 --- a/tests/data/test544 +++ b/tests/data/test544 @@ -26,9 +26,6 @@ OK http # tool is what to use instead of 'curl' - -lib%TESTNUMBER - HTTP POST text data using CURLOPT_COPYPOSTFIELDS diff --git a/tests/data/test547 b/tests/data/test547 index 143f98d7cd2b..8a26250f4ccb 100644 --- a/tests/data/test547 +++ b/tests/data/test547 @@ -74,9 +74,6 @@ Contents of that page you requested, sir. http # tool to use - -lib%TESTNUMBER - NTLM SSL diff --git a/tests/data/test549 b/tests/data/test549 index 15955bf3ba3f..e352e431f995 100644 --- a/tests/data/test549 +++ b/tests/data/test549 @@ -34,9 +34,6 @@ http ftp proxy - -lib%TESTNUMBER - FTP RETR over proxy with CURLOPT_PROXY_TRANSFER_MODE diff --git a/tests/data/test552 b/tests/data/test552 index 5bcda6a50bfd..a2b31f2a3054 100644 --- a/tests/data/test552 +++ b/tests/data/test552 @@ -52,9 +52,6 @@ Contents of that page you requested, sir. http # tool to use - -lib%TESTNUMBER - !SSPI crypto diff --git a/tests/data/test553 b/tests/data/test553 index 85c1623201c0..ec9ac75aca92 100644 --- a/tests/data/test553 +++ b/tests/data/test553 @@ -28,9 +28,6 @@ hello http # tool to use - -lib%TESTNUMBER - diff --git a/tests/data/test554 b/tests/data/test554 index ebd6a770df57..13028ad8d71e 100644 --- a/tests/data/test554 +++ b/tests/data/test554 @@ -47,9 +47,6 @@ form-api http # tool is what to use instead of 'curl' - -lib%TESTNUMBER - HTTP multi-part formpost using read callback for the file part diff --git a/tests/data/test555 b/tests/data/test555 index d5aa924338ff..073873816bf7 100644 --- a/tests/data/test555 +++ b/tests/data/test555 @@ -79,9 +79,6 @@ Contents of that page you requested, sir. http # tool to use - -lib%TESTNUMBER - NTLM SSL diff --git a/tests/data/test556 b/tests/data/test556 index 9352fe7ab18b..826ce4a9a194 100644 --- a/tests/data/test556 +++ b/tests/data/test556 @@ -25,9 +25,6 @@ Connection: close http - -lib%TESTNUMBER - send and recv HTTP diff --git a/tests/data/test557 b/tests/data/test557 index 9a23b58f6623..75e39e49ff7d 100644 --- a/tests/data/test557 +++ b/tests/data/test557 @@ -13,9 +13,6 @@ libtest # Client-side # tool is what to use instead of 'curl' - -lib%TESTNUMBER - curl_mprintf() testing diff --git a/tests/data/test558 b/tests/data/test558 index 29249ab4e904..7b3d21d63477 100644 --- a/tests/data/test558 +++ b/tests/data/test558 @@ -18,9 +18,6 @@ TrackMemory IPv6 # tool is what to use instead of 'curl' - -lib%TESTNUMBER - libtest memory tracking operational diff --git a/tests/data/test559 b/tests/data/test559 index 297aa6309d77..fe49b154becd 100644 --- a/tests/data/test559 +++ b/tests/data/test559 @@ -32,9 +32,6 @@ Silly-header: %repeat[511 x Z]% http # tool is what to use instead of 'curl' - -lib%TESTNUMBER - use tiny CURLOPT_BUFFERSIZE diff --git a/tests/data/test560 b/tests/data/test560 index 7c406f2977f6..f980e665bf44 100644 --- a/tests/data/test560 +++ b/tests/data/test560 @@ -29,9 +29,6 @@ SSL https - -lib%TESTNUMBER - simple HTTPS GET with multi interface diff --git a/tests/data/test562 b/tests/data/test562 index 01733ec56f3e..d83b02d37b74 100644 --- a/tests/data/test562 +++ b/tests/data/test562 @@ -21,9 +21,6 @@ over FTP ftp - -lib%TESTNUMBER - FTP a type=A URL and CURLOPT_PORT set diff --git a/tests/data/test564 b/tests/data/test564 index 9a4cf74ae057..f309ea200cb1 100644 --- a/tests/data/test564 +++ b/tests/data/test564 @@ -31,9 +31,6 @@ works ftp socks4 - -lib%TESTNUMBER - FTP RETR a file over a SOCKS proxy using the multi interface diff --git a/tests/data/test566 b/tests/data/test566 index 8457b9ff2d91..c922212cd30a 100644 --- a/tests/data/test566 +++ b/tests/data/test566 @@ -30,9 +30,6 @@ Funny-head: yesyes http # tool is what to use instead of 'curl' - -lib%TESTNUMBER - HTTP GET with CURLINFO_CONTENT_LENGTH_DOWNLOAD and 0 bytes transfer diff --git a/tests/data/test567 b/tests/data/test567 index 1159c222d832..bfab1a349a41 100644 --- a/tests/data/test567 +++ b/tests/data/test567 @@ -25,9 +25,6 @@ Curl-Private: swsclose rtsp - -lib%TESTNUMBER - simple RTSP OPTIONS command diff --git a/tests/data/test568 b/tests/data/test568 index 8cce6096212b..ca57db875172 100644 --- a/tests/data/test568 +++ b/tests/data/test568 @@ -50,9 +50,6 @@ Informational: Empty Options Response rtsp - -lib%TESTNUMBER - RTSP Announce (PUT and POST style) test diff --git a/tests/data/test569 b/tests/data/test569 index a9f0a971cabe..39a145929e86 100644 --- a/tests/data/test569 +++ b/tests/data/test569 @@ -58,9 +58,6 @@ CSeq: 6 rtsp - -lib%TESTNUMBER - RTSP Session ID parsing diff --git a/tests/data/test570 b/tests/data/test570 index 6009465c1ae1..58035bfb5f23 100644 --- a/tests/data/test570 +++ b/tests/data/test570 @@ -42,9 +42,6 @@ CSeq: 1000 rtsp - -lib%TESTNUMBER - RTSP CSeq and Session Mismatch checks diff --git a/tests/data/test571 b/tests/data/test571 index 675ae5c83b38..56f6f8f63c51 100644 --- a/tests/data/test571 +++ b/tests/data/test571 @@ -78,9 +78,6 @@ rtp: part 4 channel 0 size 63 rtsp - -lib%TESTNUMBER - RTSP RTP Interleaving Test diff --git a/tests/data/test572 b/tests/data/test572 index d16ecd42beb2..fb161d9b50b8 100644 --- a/tests/data/test572 +++ b/tests/data/test572 @@ -65,9 +65,6 @@ Informational: Empty Options Response rtsp - -lib%TESTNUMBER - RTSP GET_PARAMETER (Put/Heartbeat/Post) diff --git a/tests/data/test573 b/tests/data/test573 index b128bb4253c7..715256ea6e82 100644 --- a/tests/data/test573 +++ b/tests/data/test573 @@ -32,9 +32,6 @@ Funny-head: yesyes http # tool is what to use instead of 'curl' - -lib%TESTNUMBER - verify connect time with multi interface diff --git a/tests/data/test574 b/tests/data/test574 index 63674404ec2a..17430464ca4f 100644 --- a/tests/data/test574 +++ b/tests/data/test574 @@ -19,9 +19,6 @@ libtest ftp - -lib%TESTNUMBER - FTP wildcard download - changed fnmatch, 2x perform (Unix LIST) diff --git a/tests/data/test575 b/tests/data/test575 index e4b99184b651..c71afe6f1066 100644 --- a/tests/data/test575 +++ b/tests/data/test575 @@ -22,9 +22,6 @@ libtest ftp - -lib%TESTNUMBER - FTP wildcard download - dup_handle and multi interface diff --git a/tests/data/test576 b/tests/data/test576 index 5437008d13a9..312d1e5a2414 100644 --- a/tests/data/test576 +++ b/tests/data/test576 @@ -20,9 +20,6 @@ libtest ftp - -lib%TESTNUMBER - FTP wildcard - skip/parser_correctness/CURLOPT_FNMATCH_FUNCTION (Unix) diff --git a/tests/data/test578 b/tests/data/test578 index 7935bfffa6c9..856090dcac38 100644 --- a/tests/data/test578 +++ b/tests/data/test578 @@ -31,9 +31,6 @@ Funny-head: yesyes http # tool is what to use instead of 'curl' - -lib%TESTNUMBER - HTTP POST lower than MAX_INITIAL_POST_SIZE with progress callback diff --git a/tests/data/test579 b/tests/data/test579 index 7b2164b7a6fe..cef657a4f61b 100644 --- a/tests/data/test579 +++ b/tests/data/test579 @@ -62,9 +62,6 @@ crypto digest # tool is what to use instead of 'curl' - -lib%TESTNUMBER - small chunked HTTP POSTs with digest auth and progress callback diff --git a/tests/data/test582 b/tests/data/test582 index 758bc37485fc..a29961ce3cbe 100644 --- a/tests/data/test582 +++ b/tests/data/test582 @@ -19,9 +19,6 @@ libtest sftp - -lib%TESTNUMBER - SFTP upload using multi interface diff --git a/tests/data/test583 b/tests/data/test583 index 97cf60a9f8c3..0b3817090aea 100644 --- a/tests/data/test583 +++ b/tests/data/test583 @@ -19,9 +19,6 @@ libtest sftp - -lib%TESTNUMBER - SFTP with multi interface, remove handle early diff --git a/tests/data/test589 b/tests/data/test589 index b22a92c395ea..914a24719cac 100644 --- a/tests/data/test589 +++ b/tests/data/test589 @@ -30,9 +30,6 @@ Mime http # tool is what to use instead of 'curl' - -lib%TESTNUMBER - make an HTTP MIME POST set to NULL diff --git a/tests/data/test590 b/tests/data/test590 index 15266f25823e..68e8ba6c2ab6 100644 --- a/tests/data/test590 +++ b/tests/data/test590 @@ -72,9 +72,6 @@ Contents of that page you requested, sir. http # tool to use - -lib%TESTNUMBER - NTLM SSL diff --git a/tests/data/test591 b/tests/data/test591 index 72363d894017..b71fba4db624 100644 --- a/tests/data/test591 +++ b/tests/data/test591 @@ -30,9 +30,6 @@ REPLY EPRT 500 we don't like EPRT now ftp - -lib%TESTNUMBER - FTP multi PORT and 425 on upload diff --git a/tests/data/test597 b/tests/data/test597 index 41cc23070882..f3d33a118755 100644 --- a/tests/data/test597 +++ b/tests/data/test597 @@ -13,9 +13,6 @@ libtest ftp - -lib%TESTNUMBER - FTP connect only option diff --git a/tests/data/test598 b/tests/data/test598 index 1bd579c5da5c..3c8df89e8bf6 100644 --- a/tests/data/test598 +++ b/tests/data/test598 @@ -49,9 +49,6 @@ Content-Type: text/html http # tool is what to use instead of 'curl' - -lib%TESTNUMBER - curl_easy_reset with referer and other strings set diff --git a/tests/data/test599 b/tests/data/test599 index 0db3e330f3f9..e7814d40e2a6 100644 --- a/tests/data/test599 +++ b/tests/data/test599 @@ -63,9 +63,6 @@ this data is slightly larger than the first piece http # tool is what to use instead of 'curl' - -lib%TESTNUMBER - # Bug 3510057 pointed out that when following a location to a larger chunked # encoded page, the first size would still be used in the progress callback diff --git a/tests/data/test643 b/tests/data/test643 index 12cc213a557b..f5a7d701832f 100644 --- a/tests/data/test643 +++ b/tests/data/test643 @@ -47,9 +47,6 @@ Mime http # tool is what to use instead of 'curl' - -lib%TESTNUMBER - HTTP multi-part mimepost using read callback for the file part diff --git a/tests/data/test650 b/tests/data/test650 index 32ef3135c31f..64bc85428f31 100644 --- a/tests/data/test650 +++ b/tests/data/test650 @@ -42,9 +42,6 @@ form-api http # tool is what to use instead of 'curl' - -lib%TESTNUMBER - HTTP formpost using form API - with redirect and re-POST diff --git a/tests/data/test651 b/tests/data/test651 index 8a062872f5e0..2e76fafa5200 100644 --- a/tests/data/test651 +++ b/tests/data/test651 @@ -31,9 +31,6 @@ form-api http # tool is what to use instead of 'curl' - -lib%TESTNUMBER - curl_formadd with huge COPYCONTENTS diff --git a/tests/data/test652 b/tests/data/test652 index 967941a56982..bde5e36d5169 100644 --- a/tests/data/test652 +++ b/tests/data/test652 @@ -20,9 +20,6 @@ Mime smtp - -lib%TESTNUMBER - SMTP with encoded huge mime data contents diff --git a/tests/data/test653 b/tests/data/test653 index 7287da9fc421..49c400f5a259 100644 --- a/tests/data/test653 +++ b/tests/data/test653 @@ -47,9 +47,6 @@ Mime http # tool is what to use instead of 'curl' - -lib%TESTNUMBER - Reuse of handle after altering mime data diff --git a/tests/data/test654 b/tests/data/test654 index 84a4d9e40681..78810669a472 100644 --- a/tests/data/test654 +++ b/tests/data/test654 @@ -47,9 +47,6 @@ Mime http # tool is what to use instead of 'curl' - -lib%TESTNUMBER - HTTP duplicate easy handle with mime data diff --git a/tests/data/test655 b/tests/data/test655 index 5273bac81a68..e956cd901f47 100644 --- a/tests/data/test655 +++ b/tests/data/test655 @@ -29,9 +29,6 @@ hello http # tool is what to use instead of 'curl' - -lib%TESTNUMBER - resolver start callback diff --git a/tests/data/test658 b/tests/data/test658 index 441169bd81fa..17a82f0da860 100644 --- a/tests/data/test658 +++ b/tests/data/test658 @@ -28,9 +28,6 @@ Funny-head: yesyes http - -lib%TESTNUMBER - Pass URL to libcurl with CURLOPT_CURLU diff --git a/tests/data/test659 b/tests/data/test659 index c767738eca4f..222658f19353 100644 --- a/tests/data/test659 +++ b/tests/data/test659 @@ -29,9 +29,6 @@ Funny-head: yesyes http - -lib%TESTNUMBER - CURLOPT_CURLU without the path set - over proxy diff --git a/tests/data/test661 b/tests/data/test661 index 96d85a7272b6..4673daeed724 100644 --- a/tests/data/test661 +++ b/tests/data/test661 @@ -19,9 +19,6 @@ libtest ftp # tool is what to use instead of 'curl' - -lib%TESTNUMBER - Avoid redundant CWDs diff --git a/tests/data/test666 b/tests/data/test666 index d15cdeccc8fa..1539a9bd174c 100644 --- a/tests/data/test666 +++ b/tests/data/test666 @@ -38,9 +38,6 @@ Mime http - -lib%TESTNUMBER - HTTP mime post with binary-encoded huge data contents diff --git a/tests/data/test667 b/tests/data/test667 index 2085ed454071..cfd4b1a81676 100644 --- a/tests/data/test667 +++ b/tests/data/test667 @@ -40,9 +40,6 @@ Mime http # tool is what to use instead of 'curl' - -lib%TESTNUMBER - HTTP chunked mimepost using single-byte read callback with encoder diff --git a/tests/data/test668 b/tests/data/test668 index 01c186875424..06b90c2208f2 100644 --- a/tests/data/test668 +++ b/tests/data/test668 @@ -40,9 +40,6 @@ Mime http # tool is what to use instead of 'curl' - -lib%TESTNUMBER - HTTP mimepost early end of data detection diff --git a/tests/data/test670 b/tests/data/test670 index a5bd21699e79..8c30b294e75f 100644 --- a/tests/data/test670 +++ b/tests/data/test670 @@ -40,9 +40,6 @@ Mime http # tool is what to use instead of 'curl' - -lib%TESTNUMBER - Request pause from mime read callback: multi diff --git a/tests/data/test674 b/tests/data/test674 index e322b55a15e6..ff331c31dfd7 100644 --- a/tests/data/test674 +++ b/tests/data/test674 @@ -29,9 +29,6 @@ Funny-head: yesyes http - -lib%TESTNUMBER - Set CURLOPT_CURLU and dupe the handle diff --git a/tests/data/test676 b/tests/data/test676 index e61097aff2bf..95d27c9aa4b9 100644 --- a/tests/data/test676 +++ b/tests/data/test676 @@ -52,9 +52,6 @@ Content-Type: text/html http # tool is what to use instead of 'curl' - -lib%TESTNUMBER - verify setting CURLOPT_COOKIEFILE to NULL again diff --git a/tests/data/test677 b/tests/data/test677 index 942903277e62..24daaf17f8c6 100644 --- a/tests/data/test677 +++ b/tests/data/test677 @@ -22,9 +22,6 @@ imap IMAP with CONNECT_ONLY, custom command then exit - -lib%TESTNUMBER - imap://%HOSTIP:%IMAPPORT/677 diff --git a/tests/data/test678 b/tests/data/test678 index 895ebcff528b..c692c4d7b9d2 100644 --- a/tests/data/test678 +++ b/tests/data/test678 @@ -32,9 +32,6 @@ https test-localhost.pem HTTPS GET using CURLOPT_CAINFO_BLOB - -lib%TESTNUMBER - # provide URL and ca-cert https://localhost:%HTTPSPORT/%TESTNUMBER %CERTDIR/certs/test-ca.crt diff --git a/tests/data/test694 b/tests/data/test694 index 3d05997e099f..836e3926912f 100644 --- a/tests/data/test694 +++ b/tests/data/test694 @@ -87,9 +87,6 @@ Contents of that second request. Differn. http # tool to use - -lib%TESTNUMBER - NTLM !SSPI diff --git a/tests/data/test695 b/tests/data/test695 index ebfa6f9cdab2..5418d4d633ca 100644 --- a/tests/data/test695 +++ b/tests/data/test695 @@ -26,9 +26,6 @@ Mime http - -lib%TESTNUMBER - MIME parts reuse as a child part diff --git a/tests/data/test751 b/tests/data/test751 index 7e9a6165f4e5..b1713a77ffdb 100644 --- a/tests/data/test751 +++ b/tests/data/test751 @@ -14,9 +14,6 @@ libtest # Client-side # tool is what to use instead of 'curl' - -lib%TESTNUMBER - multi - add many easy handles diff --git a/tests/data/test753 b/tests/data/test753 index 2a115d2f0b53..54e6f18fad8a 100644 --- a/tests/data/test753 +++ b/tests/data/test753 @@ -16,9 +16,6 @@ ftp ftp - -lib%TESTNUMBER - cleanup easy without multi_remove_handle diff --git a/tests/data/test757 b/tests/data/test757 index 1aa710e29f42..c39392e4eade 100644 --- a/tests/data/test757 +++ b/tests/data/test757 @@ -26,9 +26,6 @@ Mime http - -lib%TESTNUMBER - MIME parts reused as a child part, using data_cb diff --git a/tests/data/test758 b/tests/data/test758 index f7a610803121..140c630e4664 100644 --- a/tests/data/test758 +++ b/tests/data/test758 @@ -36,9 +36,6 @@ OpenSSL https - -lib%TESTNUMBER - HTTPS multi_socket interface transfer with callbacks returning error diff --git a/tests/data/test766 b/tests/data/test766 index e3f3b6034ab4..c64e2d2ff4b0 100644 --- a/tests/data/test766 +++ b/tests/data/test766 @@ -22,9 +22,6 @@ hello ftp - -lib%TESTNUMBER - FTP PORT with sockopt callback refusing the accept From 6c11cd428d8dad2c3502965d7c28da9b5ac48697 Mon Sep 17 00:00:00 2001 From: Daniel Stenberg Date: Tue, 22 Sep 2026 17:16:39 +0200 Subject: [PATCH 210/481] cf-socket: clamp keepalive seconds-to-milliseconds conversions On platforms that require keepalive times in milliseconds (Windows before 10.0.16299, Solaris before 11.4, DragonFlyBSD before 500702), curl converts seconds to milliseconds and starting now it prevents that multiplication from doing an integer overflow. If too large value is set, INT_MAX millisconds is used. Reported-by: Joshua Rogers Closes #23045 --- lib/cf-socket.c | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/lib/cf-socket.c b/lib/cf-socket.c index 3c84bf24bfe8..379c3bfb7ccb 100644 --- a/lib/cf-socket.c +++ b/lib/cf-socket.c @@ -177,9 +177,17 @@ static void tcpnodelay(struct Curl_cfilter *cf, (defined(_WIN32) && !defined(TCP_KEEPIDLE)) /* Solaris < 11.4, DragonFlyBSD < 500702 and Windows < 10.0.16299 * use millisecond units. */ -#define KEEPALIVE_FACTOR(x) ((x) *= 1000) +static void alive_unit(int *x) +{ + /* make sure this doesn't wrap */ + if(*x < (INT_MAX / 1000)) + *x *= 1000; + else + /* this is still almost 25 days */ + *x = INT_MAX; +} #else -#define KEEPALIVE_FACTOR(x) +#define alive_unit(x) #endif #endif @@ -250,10 +258,10 @@ static void tcpkeepalive(struct Curl_cfilter *cf, DWORD dummy; vals.onoff = 1; optval = curlx_sltosi(data->set.tcp_keepidle); - KEEPALIVE_FACTOR(optval); + alive_unit(&optval); vals.keepalivetime = (u_long)optval; optval = curlx_sltosi(data->set.tcp_keepintvl); - KEEPALIVE_FACTOR(optval); + alive_unit(&optval); vals.keepaliveinterval = (u_long)optval; if(WSAIoctl(sockfd, SIO_KEEPALIVE_VALS, (LPVOID)&vals, sizeof(vals), NULL, 0, &dummy, NULL, NULL) != 0) { @@ -264,7 +272,7 @@ static void tcpkeepalive(struct Curl_cfilter *cf, #else /* !USE_WINSOCK */ #ifdef TCP_KEEPIDLE optval = curlx_sltosi(data->set.tcp_keepidle); - KEEPALIVE_FACTOR(optval); + alive_unit(&optval); if(setsockopt(sockfd, IPPROTO_TCP, TCP_KEEPIDLE, (void *)&optval, sizeof(optval)) < 0) { CURL_TRC_CF(data, cf, "Failed to set TCP_KEEPIDLE on fd " @@ -273,7 +281,7 @@ static void tcpkeepalive(struct Curl_cfilter *cf, #elif defined(TCP_KEEPALIVE) /* macOS style */ optval = curlx_sltosi(data->set.tcp_keepidle); - KEEPALIVE_FACTOR(optval); + alive_unit(&optval); if(setsockopt(sockfd, IPPROTO_TCP, TCP_KEEPALIVE, (void *)&optval, sizeof(optval)) < 0) { CURL_TRC_CF(data, cf, "Failed to set TCP_KEEPALIVE on fd " @@ -282,7 +290,7 @@ static void tcpkeepalive(struct Curl_cfilter *cf, #elif defined(TCP_KEEPALIVE_THRESHOLD) /* Solaris <11.4 style */ optval = curlx_sltosi(data->set.tcp_keepidle); - KEEPALIVE_FACTOR(optval); + alive_unit(&optval); if(setsockopt(sockfd, IPPROTO_TCP, TCP_KEEPALIVE_THRESHOLD, (void *)&optval, sizeof(optval)) < 0) { CURL_TRC_CF(data, cf, "Failed to set TCP_KEEPALIVE_THRESHOLD on fd " @@ -291,7 +299,7 @@ static void tcpkeepalive(struct Curl_cfilter *cf, #endif #ifdef TCP_KEEPINTVL optval = curlx_sltosi(data->set.tcp_keepintvl); - KEEPALIVE_FACTOR(optval); + alive_unit(&optval); if(setsockopt(sockfd, IPPROTO_TCP, TCP_KEEPINTVL, (void *)&optval, sizeof(optval)) < 0) { CURL_TRC_CF(data, cf, "Failed to set TCP_KEEPINTVL on fd " @@ -318,7 +326,7 @@ static void tcpkeepalive(struct Curl_cfilter *cf, else optval = keepcnt * keepintvl; } - KEEPALIVE_FACTOR(optval); + alive_unit(&optval); if(setsockopt(sockfd, IPPROTO_TCP, TCP_KEEPALIVE_ABORT_THRESHOLD, (void *)&optval, sizeof(optval)) < 0) { CURL_TRC_CF(data, cf, "Failed to set TCP_KEEPALIVE_ABORT_THRESHOLD" From 13e84a694584531038ed3461c038fafd78262e2e Mon Sep 17 00:00:00 2001 From: Joshua Rogers Date: Wed, 16 Sep 2026 01:52:54 +0200 Subject: [PATCH 211/481] urlapi: make curl_url_dup() copy the guessed_scheme flag curl_url_dup() copied port_present, fragment_present and query_present but missed guessed_scheme, so a duplicated handle always reported an explicit scheme. Applications relying on CURLU_GUESS_SCHEME plus CURLU_NO_GUESS_SCHEME to reject bare hostnames after duplicating a handle would silently accept them instead. Verified in test 1989 Edited-by: Daniel Stenberg Reported-by: Joshua Rogers Closes #23043 --- lib/urlapi.c | 1 + tests/data/Makefile.am | 2 +- tests/data/test1989 | 18 ++++++++++ tests/libtest/Makefile.inc | 1 + tests/libtest/lib1989.c | 67 ++++++++++++++++++++++++++++++++++++++ 5 files changed, 88 insertions(+), 1 deletion(-) create mode 100644 tests/data/test1989 create mode 100644 tests/libtest/lib1989.c diff --git a/lib/urlapi.c b/lib/urlapi.c index 003ed2f8a1a1..e64adb80277e 100644 --- a/lib/urlapi.c +++ b/lib/urlapi.c @@ -1448,6 +1448,7 @@ CURLU *curl_url_dup(const CURLU *in) u->port_present = in->port_present; u->fragment_present = in->fragment_present; u->query_present = in->query_present; + u->guessed_scheme = in->guessed_scheme; } return u; fail: diff --git a/tests/data/Makefile.am b/tests/data/Makefile.am index 96af28719eed..39c388b50d4d 100644 --- a/tests/data/Makefile.am +++ b/tests/data/Makefile.am @@ -1823,7 +1823,7 @@ TESTCASES = \ test1983 \ test1984 \ test1985 \ - \ + test1989 \ test2000 \ test2001 \ test2002 \ diff --git a/tests/data/test1989 b/tests/data/test1989 new file mode 100644 index 000000000000..671947b9bb37 --- /dev/null +++ b/tests/data/test1989 @@ -0,0 +1,18 @@ + + + + +libtest +urlapi + + + + + +lib%TESTNUMBER + + +curl_url_dup preserves guessed_scheme + + + diff --git a/tests/libtest/Makefile.inc b/tests/libtest/Makefile.inc index b3a4cf8fe8ca..d1b4204ce076 100644 --- a/tests/libtest/Makefile.inc +++ b/tests/libtest/Makefile.inc @@ -298,6 +298,7 @@ TESTS_C = \ lib1977.c \ lib1978.c \ lib1985.c \ + lib1989.c \ lib2023.c \ lib2032.c \ lib2082.c \ diff --git a/tests/libtest/lib1989.c b/tests/libtest/lib1989.c new file mode 100644 index 000000000000..ef331b53d3b8 --- /dev/null +++ b/tests/libtest/lib1989.c @@ -0,0 +1,67 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ +#include "unitcheck.h" +#include "urldata.h" +#include "curl/urlapi.h" + +static CURLcode test_lib1989(const char *arg) +{ + UNITTEST_BEGIN_SIMPLE + + CURLU *u = NULL; + CURLU *dup = NULL; + CURLUcode uc; + char *scheme = NULL; + + /* Parse a scheme-less URL, letting the scheme be guessed */ + u = curl_url(); + if(!u) + goto fail; + uc = curl_url_set(u, CURLUPART_URL, "example.com/path", CURLU_GUESS_SCHEME); + fail_unless(uc == CURLUE_OK, "curl_url_set with CURLU_GUESS_SCHEME failed"); + + /* The original handle should reject the scheme with CURLU_NO_GUESS_SCHEME + since the scheme was only guessed, not explicit */ + uc = curl_url_get(u, CURLUPART_SCHEME, &scheme, CURLU_NO_GUESS_SCHEME); + fail_unless(uc == CURLUE_NO_SCHEME, + "original handle did not reject guessed scheme"); + curl_free(scheme); + scheme = NULL; + + dup = curl_url_dup(u); + if(!dup) + goto fail; + + /* The duplicate must still remember that the scheme was guessed */ + uc = curl_url_get(dup, CURLUPART_SCHEME, &scheme, CURLU_NO_GUESS_SCHEME); + fail_unless(uc == CURLUE_NO_SCHEME, + "duplicated handle did not reject guessed scheme"); + curl_free(scheme); + +fail: + curl_url_cleanup(dup); + curl_url_cleanup(u); + + UNITTEST_END_SIMPLE +} From fa8412ca249caef7670972330653b2e7ba8b60db Mon Sep 17 00:00:00 2001 From: Stefan Eissing Date: Tue, 22 Sep 2026 10:13:24 +0200 Subject: [PATCH 212/481] uint-table: add remove-max precaution Removing key UINT32_MAX is always a NOP, since that key cannot be in the table. This was ok due to a wrap-around, but make it explicit. Reported-by: Joshua Rogers Closes #23034 --- lib/uint-table.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/uint-table.c b/lib/uint-table.c index 50427a891a09..71b52999830c 100644 --- a/lib/uint-table.c +++ b/lib/uint-table.c @@ -149,7 +149,8 @@ bool Curl_uint32_tbl_add(struct uint32_tbl *tbl, void *entry, uint32_t *pkey) void Curl_uint32_tbl_remove(struct uint32_tbl *tbl, uint32_t key) { - uint32_tbl_clear_rows(tbl, key, key + 1); + if(key < tbl->nrows) + uint32_tbl_clear_rows(tbl, key, key + 1); } bool Curl_uint32_tbl_contains(struct uint32_tbl *tbl, uint32_t key) From 4c67658f953751de9e66bb5f9729af821a641341 Mon Sep 17 00:00:00 2001 From: Filippo Tedeschi Date: Mon, 21 Sep 2026 18:42:30 +0200 Subject: [PATCH 213/481] mime: bound Curl_mime_duppart() recursion depth Curl_mime_duppart() duplicates multipart structures recursively when cloning an easy handle via curl_easy_duphandle(). Previously, this traversal did not enforce a recursion limit, allowing an excessively deep MIME tree to cause unbounded recursion and stack exhaustion. Bound recursion in mime_duppart() to MAX_MIME_LEVELS, returning CURLE_TOO_LARGE when exceeded, following the precedent set for mime_prepare_headers() in c427e91. Extended test 3596 to verify that curl_easy_duphandle() safely fails for deeply nested MIME trees. Closes #23023 --- lib/mime.c | 43 +++++++++++++++++++++++++++++++--- tests/libtest/lib3596.c | 51 ++++++++++++++++++++++++++++++++++++++++- 2 files changed, 90 insertions(+), 4 deletions(-) diff --git a/lib/mime.c b/lib/mime.c index 357152f5bc0f..f491f22f95ac 100644 --- a/lib/mime.c +++ b/lib/mime.c @@ -1123,8 +1123,29 @@ void curl_mime_free(curl_mime *mime) } } -CURLcode Curl_mime_duppart(struct Curl_easy *data, - curl_mimepart *dst, const curl_mimepart *src) +static bool mime_is_too_deep(const curl_mimepart *part, size_t call_depth) +{ + DEBUGASSERT(part); + + if(++call_depth > MAX_MIME_LEVELS) + return TRUE; + + if(part->kind == MIMEKIND_MULTIPART && part->arg) { + const curl_mime *mime = (const curl_mime *)part->arg; + const curl_mimepart *s; + + for(s = mime->firstpart; s; s = s->nextpart) { + if(mime_is_too_deep(s, call_depth)) + return TRUE; + } + } + + return FALSE; +} + +static CURLcode mime_duppart(struct Curl_easy *data, + curl_mimepart *dst, const curl_mimepart *src, + size_t call_depth) { curl_mime *mime; curl_mimepart *d; @@ -1132,6 +1153,10 @@ CURLcode Curl_mime_duppart(struct Curl_easy *data, CURLcode result = CURLE_OK; DEBUGASSERT(dst); + DEBUGASSERT(src); + + if(++call_depth > MAX_MIME_LEVELS) + return CURLE_TOO_LARGE; /* Duplicate content. */ switch(src->kind) { @@ -1160,7 +1185,7 @@ CURLcode Curl_mime_duppart(struct Curl_easy *data, for(s = ((curl_mime *)src->arg)->firstpart; !result && s; s = s->nextpart) { d = curl_mime_addpart(mime); - result = d ? Curl_mime_duppart(data, d, s) : CURLE_OUT_OF_MEMORY; + result = d ? mime_duppart(data, d, s, call_depth) : CURLE_OUT_OF_MEMORY; } break; default: /* Invalid kind: should not occur. */ @@ -1201,6 +1226,18 @@ CURLcode Curl_mime_duppart(struct Curl_easy *data, return result; } +CURLcode Curl_mime_duppart(struct Curl_easy *data, + curl_mimepart *dst, const curl_mimepart *src) +{ + if(!dst || !src) + return CURLE_BAD_FUNCTION_ARGUMENT; + + if(mime_is_too_deep(src, 0)) + return CURLE_TOO_LARGE; + + return mime_duppart(data, dst, src, 0); +} + /* * Mime build functions. */ diff --git a/tests/libtest/lib3596.c b/tests/libtest/lib3596.c index 7a55859edd36..61b8f65f6d02 100644 --- a/tests/libtest/lib3596.c +++ b/tests/libtest/lib3596.c @@ -23,6 +23,25 @@ ***************************************************************************/ #include "unitcheck.h" +#if !defined(CURL_DISABLE_MIME) && !defined(CURL_DISABLE_HTTP) +static int callback_free_count = 0; + +static void test_cb_free(void *ptr) +{ + (void)ptr; + callback_free_count++; +} + +static size_t test_cb_read(char *buffer, size_t size, size_t nitems, void *arg) +{ + (void)buffer; + (void)size; + (void)nitems; + (void)arg; + return 0; +} +#endif + static CURLcode test_lib3596(const char *arg) { UNITTEST_BEGIN_SIMPLE @@ -37,6 +56,8 @@ static CURLcode test_lib3596(const char *arg) curl_mimepart *part = NULL; int i; + callback_free_count = 0; + if(curl_global_init(CURL_GLOBAL_ALL) != CURLE_OK) { curl_mfprintf(stderr, "curl_global_init() failed\n"); return TEST_ERR_MAJOR_BAD; @@ -50,6 +71,15 @@ static CURLcode test_lib3596(const char *arg) stack. */ root = curl_mime_init(curl); fail_unless(root != NULL, "curl_mime_init root failed"); + + /* Add a sibling part with a custom free callback before the deep chain + to verify that preflight depth rejection does not duplicate callbacks + or invoke them during error rollback. */ + part = curl_mime_addpart(root); + fail_unless(part != NULL, "curl_mime_addpart sibling failed"); + result = curl_mime_data_cb(part, 0, test_cb_read, NULL, test_cb_free, NULL); + fail_unless(result == CURLE_OK, "curl_mime_data_cb failed"); + current = root; for(i = 0; i < 50; i++) { @@ -67,14 +97,33 @@ static CURLcode test_lib3596(const char *arg) result = curl_easy_setopt(curl, CURLOPT_MIMEPOST, root); fail_unless(result == CURLE_OK, "CURLOPT_MIMEPOST failed"); - /* Deep nesting must fail gracefully (CURLE_TOO_LARGE), not crash. */ + /* Deep nesting must fail gracefully (CURLE_TOO_LARGE), not crash, and + must not invoke copied user free callbacks during error rollback. */ + if(!result) { + CURL *dup = curl_easy_duphandle(curl); + fail_unless(dup == NULL, + "curl_easy_duphandle unexpectedly succeeded for nested mime"); + fail_unless(callback_free_count == 0, + "free callback invoked during failed duphandle rollback"); + } + if(!result) result = curl_easy_perform(curl); fail_unless(result == CURLE_TOO_LARGE, "deeply nested mime did not fail with CURLE_TOO_LARGE"); + { + CURL *dup = curl_easy_duphandle(curl); + fail_unless(dup == NULL, + "curl_easy_duphandle unexpectedly succeeded after perform"); + fail_unless(callback_free_count == 0, + "free callback invoked after perform duphandle"); + } + curl_easy_cleanup(curl); curl_mime_free(root); + fail_unless(callback_free_count == 1, + "free callback not invoked during root cleanup"); curl_global_cleanup(); #else (void)arg; From 8aafb2adb21b5120252c2346bd1ee8141a19c981 Mon Sep 17 00:00:00 2001 From: Daniel Stenberg Date: Wed, 23 Sep 2026 09:29:15 +0200 Subject: [PATCH 214/481] libtest/lib464.c: provide INADDR_LOOPBACK if not present Apparently there's an AmigaOS build without it... Follow-up to f71efc7a8bc3bd841017aaa58 Closes #23049 --- tests/libtest/lib464.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/libtest/lib464.c b/tests/libtest/lib464.c index 669021a1a9fa..0bb5d3910c1c 100644 --- a/tests/libtest/lib464.c +++ b/tests/libtest/lib464.c @@ -59,6 +59,10 @@ static int t464_resolver_start_cb(void *resolver_state, void *reserved, return 0; } +#ifndef INADDR_LOOPBACK +#define INADDR_LOOPBACK 0x7f000001 +#endif + static CURLcode test_lib464(const char *URL) { CURL *curl = NULL; From 8807773c6af3106dbadb2aad5a2dc9488a3ac8d0 Mon Sep 17 00:00:00 2001 From: Daniel Stenberg Date: Wed, 23 Sep 2026 10:55:26 +0200 Subject: [PATCH 215/481] test: restore the old test 1609 as 3271 It was wrongly removed as a duplicate but the code remained in tests/unit and was never exercised. The internal dns cache logic and API changed in the meantime so test is also updated to reflect those. Follow-up to 4ead4285a6af5d5645d Closes #23052 --- lib/vdns/dnscache.c | 25 ++++++++------------- lib/vdns/dnscache.h | 14 ++++++++++++ tests/data/Makefile.am | 2 +- tests/data/test3271 | 21 ++++++++++++++++++ tests/unit/Makefile.inc | 2 +- tests/unit/{unit1609.c => unit3271.c} | 32 +++++++++++++++------------ 6 files changed, 64 insertions(+), 32 deletions(-) create mode 100644 tests/data/test3271 rename tests/unit/{unit1609.c => unit3271.c} (89%) diff --git a/lib/vdns/dnscache.c b/lib/vdns/dnscache.c index 1c89086aafce..08570a80ed7a 100644 --- a/lib/vdns/dnscache.c +++ b/lib/vdns/dnscache.c @@ -56,18 +56,13 @@ #include "curlx/strcopy.h" #include "curlx/strparse.h" -#define MAX_HOSTCACHE_LEN (255 + 3) /* max FQDN + type + port */ - #define MAX_DNS_CACHE_SIZE 29999 -struct dnsc_id { - struct Curl_str name; - uint16_t port; - char type; -}; - -static void dnsc_peer2id(struct dnsc_id *pid, char type, - struct Curl_peer *peer) +/* @unittest 3217 */ +UNITTEST void dnsc_peer2id(struct dnsc_id *pid, char type, + struct Curl_peer *peer); +UNITTEST void dnsc_peer2id(struct dnsc_id *pid, char type, + struct Curl_peer *peer) { curlx_str_assign(&pid->name, peer->hostname, strlen(peer->hostname)); pid->port = peer->port; @@ -82,16 +77,14 @@ static void dnsc_str2id(struct dnsc_id *pid, char type, pid->type = type; } -struct dnsc_key { - uint8_t data[MAX_HOSTCACHE_LEN]; - size_t len; -}; - /* * Create a hostcache id string for the provided host + port, to be used by * the DNS caching. Without alloc. Return length of the id string. + * + * @unittest 3217 */ -static void dnsc_id2key(struct dnsc_key *key, struct dnsc_id *id) +UNITTEST void dnsc_id2key(struct dnsc_key *key, struct dnsc_id *id); +UNITTEST void dnsc_id2key(struct dnsc_key *key, struct dnsc_id *id) { size_t namelen = curlx_strlen(&id->name); if(namelen > (sizeof(key->data) - 3)) diff --git a/lib/vdns/dnscache.h b/lib/vdns/dnscache.h index bee5f78fb11d..0b47710b7f1d 100644 --- a/lib/vdns/dnscache.h +++ b/lib/vdns/dnscache.h @@ -27,6 +27,7 @@ #include "hash.h" #include "curlx/timeval.h" +#include "curlx/strparse.h" struct addrinfo; struct hostent; @@ -58,6 +59,19 @@ struct Curl_dns_entry { char hostname[1]; }; +struct dnsc_id { + struct Curl_str name; + uint16_t port; + char type; +}; + +#define MAX_HOSTCACHE_LEN (255 + 3) /* max FQDN + type + port */ + +struct dnsc_key { + uint8_t data[MAX_HOSTCACHE_LEN]; + size_t len; +}; + /* * Create a `Curl_dns_entry` with a reference count of 1. * Use `Curl_dns_entry_unlink()` to release your hold on it. diff --git a/tests/data/Makefile.am b/tests/data/Makefile.am index 39c388b50d4d..0ce48d4f1d1c 100644 --- a/tests/data/Makefile.am +++ b/tests/data/Makefile.am @@ -2116,7 +2116,7 @@ TESTCASES = \ test3231 \ test3232 \ test3233 \ - \ + test3271 \ test3300 \ test3301 \ test3302 \ diff --git a/tests/data/test3271 b/tests/data/test3271 new file mode 100644 index 000000000000..f87707e99a0b --- /dev/null +++ b/tests/data/test3271 @@ -0,0 +1,21 @@ + + + + +unittest +CURLOPT_RESOLVE + + + +# Client-side + + +unittest + + +# this test extends 1607 - they should probably be combined one day + +CURLOPT_RESOLVE override + + + diff --git a/tests/unit/Makefile.inc b/tests/unit/Makefile.inc index 897bfe6d8f9e..e6ee7302b879 100644 --- a/tests/unit/Makefile.inc +++ b/tests/unit/Makefile.inc @@ -49,7 +49,6 @@ TESTS_C = \ unit1606.c \ unit1607.c \ unit1608.c \ - unit1609.c \ unit1610.c \ unit1611.c \ unit1612.c \ @@ -102,6 +101,7 @@ TESTS_C = \ unit3219.c \ unit3227.c \ unit3230.c \ + unit3271.c \ unit3300.c \ unit3301.c \ unit3302.c \ diff --git a/tests/unit/unit1609.c b/tests/unit/unit3271.c similarity index 89% rename from tests/unit/unit1609.c rename to tests/unit/unit3271.c index 076bf21499c5..c08a7c92e6a6 100644 --- a/tests/unit/unit1609.c +++ b/tests/unit/unit3271.c @@ -26,7 +26,7 @@ #include "connect.h" #include "curl_addrinfo.h" -static CURLcode t1609_setup(void) +static CURLcode t3271_setup(void) { CURLcode result = CURLE_OK; global_init(CURL_GLOBAL_ALL); @@ -66,9 +66,9 @@ static CURLcode t1609_setup(void) expected result: cached address has zero timestamp and new address */ -static CURLcode test_unit1609(const char *arg) +static CURLcode test_unit3271(const char *arg) { - UNITTEST_BEGIN(t1609_setup()) + UNITTEST_BEGIN(t3271_setup()) struct testcase { /* host:port:address[,address]... */ @@ -76,14 +76,14 @@ static CURLcode test_unit1609(const char *arg) /* lowercase host and port to retrieve the addresses from hostcache */ const char *host; - int port; + uint16_t port; /* 0 to 9 addresses expected from hostcache */ const char *address[10]; }; static const struct testcase tests[] = { - /* spaces are not allowed, for now */ + /* spaces are not allowed */ { "test.com:80:127.0.0.1", "test.com", 80, { "127.0.0.1", } }, @@ -97,17 +97,20 @@ static CURLcode test_unit1609(const char *arg) struct Curl_easy *easy = NULL; struct curl_slist *list = NULL; - /* important: we setup cache outside of the loop - and also clean cache after the loop. In contrast, for example, - test 1607 sets up and cleans cache on each iteration. */ + /* important: we setup cache outside of the loop and also clean cache after + the loop. In contrast, for example, test 1607 sets up and cleans cache on + each iteration. */ for(i = 0; i < CURL_ARRAYSIZE(tests); ++i) { size_t j; size_t addressnum = CURL_ARRAYSIZE(tests[i].address); struct Curl_addrinfo *addr; struct Curl_dns_entry *dns; - void *entry_id; + struct dnsc_id id; + struct dnsc_key key; + const char type = CURL_DNST_ADDR; bool problem = FALSE; + struct Curl_peer *peer = NULL; easy = curl_easy_init(); if(!easy) { curl_global_cleanup(); @@ -129,13 +132,13 @@ static CURLcode test_unit1609(const char *arg) if(Curl_loadhostpairs(easy)) goto error; - entry_id = (void *)curl_maprintf("%s:%d", tests[i].host, tests[i].port); - if(!entry_id) + if(Curl_peer_create(easy, &Curl_scheme_http, + tests[i].host, tests[i].port, &peer)) goto error; - dns = Curl_hash_pick(&multi->dnscache.entries, - entry_id, strlen(entry_id) + 1); - curlx_safefree(entry_id); + dnsc_peer2id(&id, type, peer); + dnsc_id2key(&key, &id); + dns = Curl_hash_pick(&multi->dnscache.entries, key.data, key.len); addr = dns ? dns->addr : NULL; @@ -194,6 +197,7 @@ static CURLcode test_unit1609(const char *arg) addr = addr->ai_next; } + Curl_peer_unlink(&peer); curl_easy_cleanup(easy); easy = NULL; From 1750c4b27fc1eeb370c0ff6022d1e2256fe17055 Mon Sep 17 00:00:00 2001 From: Daniel Stenberg Date: Wed, 23 Sep 2026 10:24:42 +0200 Subject: [PATCH 216/481] test-linter: verify test cases Run it in a few CI jobs. - Verify that tests with source code in libtests/ have 'libtest' as - Verify that tests with source code in tunit/ have 'tunittest' as - Verify that tests with source code in unit/ have 'unittest' as - Verify that tests that set lib* in have 'libtest' as - Verify that tests that set tool* in have 'tunittest' as - Verify that tests that set unit* in have 'unittest' as - Verify that unit tests actually use private symbols - Verify that all tests have tag on first line Also introduce 'tunittest' as a keyword for the tool unit tests, separate from the library unit tests that are still tagged 'unittest'. Closes #23059 --- .github/workflows/linux.yml | 7 +- docs/tests/FILEFORMAT.md | 10 +- tests/Makefile.am | 1 + tests/data/test1394 | 5 +- tests/data/test1604 | 5 +- tests/data/test1621 | 5 +- tests/data/test1622 | 5 +- tests/data/test1623 | 5 +- tests/data/test1720 | 5 +- tests/data/test1989 | 3 - tests/runner.pm | 9 +- tests/test-linter.pl | 351 ++++++++++++++++++++++++++++++++++++ 12 files changed, 379 insertions(+), 32 deletions(-) create mode 100755 tests/test-linter.pl diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index f5ad601ab587..b7e0e6afe5a1 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -76,7 +76,7 @@ jobs: matrix: build: - name: 'awslc' - install_steps: awslc pytest + install_steps: awslc pytest test-linter LDFLAGS: -Wl,-rpath,/home/runner/awslc/lib configure: --with-openssl=/home/runner/awslc --enable-ech --enable-ntlm @@ -926,6 +926,11 @@ jobs: make -C bld V=1 -C tests fi + - name: 'test-linter' + if: ${{ contains(matrix.build.install_steps, 'test-linter') }} + run: + cd tests && ./test-linter.pl ../bld + - name: 'run tests' if: ${{ !contains(matrix.build.install_steps, 'skipall') && !contains(matrix.build.install_steps, 'skiprun') }} timeout-minutes: ${{ contains(matrix.build.install_packages, 'valgrind') && 20 || 10 }} diff --git a/docs/tests/FILEFORMAT.md b/docs/tests/FILEFORMAT.md index 3ef4f0f81990..f77228d4c336 100644 --- a/docs/tests/FILEFORMAT.md +++ b/docs/tests/FILEFORMAT.md @@ -244,8 +244,9 @@ for users to skip certain classes of tests, if desired, but a few are treated specially by the test harness or build system. When running a unit test and the keywords include `unittest`, the `` -section can be left empty to use the standard unit test tool name `unitN` where -`N` is the test number. +section can be left empty to use the standard unit test tool name `unitN` +where `N` is the test number. Unit tests means *libcurl* unit tests. The +command line tool's unit tests are marked as `tunittest`. The `test-ci` make target automatically skips test with the `flaky` keyword. @@ -540,7 +541,7 @@ Features testable here are: - `typecheck` - `threadsafe` - `Unicode` -- `unittest` +- `unittest` - that feature needs to be enabled also for the tool unit tests marked as `tunittest` - `UnixSockets` - `verbose-strings` - `wakeup` @@ -575,6 +576,9 @@ the `unit/` directory (if the tool name starts with `unit`). If the keyword `unittest` is set in the test case, the default name for the tool is `unit%TESTNUMBER`. +If the keyword `tunittest` is set in the test case, the default name for the +tool is `tool%TESTNUMBER`. + If the keyword `libtest` is set in the test case, the default name for the tool is `lib%TESTNUMBER`. diff --git a/tests/Makefile.am b/tests/Makefile.am index ba293f2b8543..0c90ccf3ca3a 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -74,6 +74,7 @@ EXTRA_DIST = \ servers.pm \ sshhelp.pm \ sshserver.pl \ + test-linter.pl \ testcurl.pl \ testutil.pm \ tftpserver.pl \ diff --git a/tests/data/test1394 b/tests/data/test1394 index 500512c0ea2b..373cc8ebd087 100644 --- a/tests/data/test1394 +++ b/tests/data/test1394 @@ -2,7 +2,7 @@ -unittest +tunittest @@ -14,9 +14,6 @@ unittest unit test for parse_cert_parameter() - -tool%TESTNUMBER - diff --git a/tests/data/test1604 b/tests/data/test1604 index 79bcd0818ae8..9bf67e43c575 100644 --- a/tests/data/test1604 +++ b/tests/data/test1604 @@ -2,7 +2,7 @@ -unittest +tunittest @@ -14,8 +14,5 @@ unittest Test Windows/MS-DOS filename sanitization - -tool%TESTNUMBER - diff --git a/tests/data/test1621 b/tests/data/test1621 index 19f9edb5a96b..ecee477da4a6 100644 --- a/tests/data/test1621 +++ b/tests/data/test1621 @@ -2,7 +2,7 @@ -unittest +tunittest stripcredentials @@ -15,8 +15,5 @@ unittest unit tests for stripcredentials from URL - -tool%TESTNUMBER - diff --git a/tests/data/test1622 b/tests/data/test1622 index 59895a60c660..534202bddd0c 100644 --- a/tests/data/test1622 +++ b/tests/data/test1622 @@ -2,7 +2,7 @@ -unittest +tunittest tool-progress @@ -15,9 +15,6 @@ unittest tool time and size progress output - -tool%TESTNUMBER - diff --git a/tests/data/test1623 b/tests/data/test1623 index 000c08a2ef32..73cf40d0765d 100644 --- a/tests/data/test1623 +++ b/tests/data/test1623 @@ -2,7 +2,7 @@ -unittest +tunittest tool-sizeparser @@ -15,9 +15,6 @@ unittest the size parser for --limit-rate - -tool%TESTNUMBER - diff --git a/tests/data/test1720 b/tests/data/test1720 index 22edb19bafa5..1cbcb967d3de 100644 --- a/tests/data/test1720 +++ b/tests/data/test1720 @@ -2,7 +2,7 @@ -unittest +tunittest @@ -14,9 +14,6 @@ unittest create_dir_hierarchy() - -tool%TESTNUMBER - diff --git a/tests/data/test1989 b/tests/data/test1989 index 671947b9bb37..721f3655be28 100644 --- a/tests/data/test1989 +++ b/tests/data/test1989 @@ -8,9 +8,6 @@ urlapi - -lib%TESTNUMBER - curl_url_dup preserves guessed_scheme diff --git a/tests/runner.pm b/tests/runner.pm index 36f4520ab42b..627307104d97 100644 --- a/tests/runner.pm +++ b/tests/runner.pm @@ -862,7 +862,10 @@ sub singletest_run { $tool = $CMDLINE; $disablevalgrind = 1; } - elsif(!$tool && (!$keywords{"unittest"} && !$keywords{"libtest"})) { + elsif(!$tool && + !$keywords{"unittest"} && + !$keywords{"tunittest"} && + !$keywords{"libtest"}) { # run curl, add suitable command line options my $inc = ""; if((!$cmdhash{'option'}) || ($cmdhash{'option'} !~ /no-include/)) { @@ -912,6 +915,10 @@ sub singletest_run { $tool_name = "lib$testnum"; $tool = $tool_name . exe_ext('TOOL'); } + elsif($keywords{"tunittest"} && !$tool) { + $tool_name = "tool$testnum"; + $tool = $tool_name . exe_ext('TOOL'); + } if($tool =~ /^lib/) { $tool = "libtests" . exe_ext('TOOL'); diff --git a/tests/test-linter.pl b/tests/test-linter.pl new file mode 100755 index 000000000000..9b7705c24dff --- /dev/null +++ b/tests/test-linter.pl @@ -0,0 +1,351 @@ +#!/usr/bin/env perl +#*************************************************************************** +# _ _ ____ _ +# Project ___| | | | _ \| | +# / __| | | | |_) | | +# | (__| |_| | _ <| |___ +# \___|\___/|_| \_\_____| +# +# Copyright (C) Daniel Stenberg, , et al. +# +# This software is licensed as described in the file COPYING, which +# you should have received as part of this distribution. The terms +# are also available at https://curl.se/docs/copyright.html. +# +# You may opt to use, copy, modify, merge, publish, distribute and/or sell +# copies of the Software, and permit persons to whom the Software is +# furnished to do so, under the terms of the COPYING file. +# +# This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY +# KIND, either express or implied. +# +# SPDX-License-Identifier: curl +# +########################################################################### + +# If the build is done out-of-source-tree, specify the build root dir as an +# argument to this script. Necessary to find the generated buildprotos.h +# header file. +my $root = ".."; + +if($ARGV[0]) { + $root = $ARGV[0]; +} + +# return all known test cases +sub alltests { + my @a; + open(M, "<./data/Makefile.am") || + print STDERR "can't open data/Makefile.am\n"; + while() { + if(/^ test(\d+)/) { + push @a, $1; # just the number + } + } + close(M); + return @a; +} + +sub libtests { + my @a; + open(M, "<./libtest/Makefile.inc") || + print STDERR "can't open Makefile.inc\n"; + while() { + if(/^ *lib(\d+)\.c/) { + push @a, $1; # just the number + } + } + close(M); + return @a; +} + +sub tunittests { + my @a; + open(M, "<./tunit/Makefile.inc") || + print STDERR "can't open Makefile.inc\n"; + while() { + if(/^ *tool(\d+)\.c/) { + push @a, $1; # just the number + } + } + close(M); + return @a; +} + +sub unittests { + my @a; + open(M, "<./unit/Makefile.inc") || + print STDERR "can't open Makefile.inc\n"; + while() { + if(/^ *unit(\d+)\.c/) { + push @a, $1; # just the number + } + } + close(M); + return @a; +} + +# provided a test number, return the keywords it uses +sub keywords { + my ($num) = @_; + open(my $fh, "<./data/test$num") || + print STDERR "can't open test$num\n"; + local $/; + my $c = <$fh>; + close($fh); + + # Match and extract the block + if ($c =~ /\n(.*?)<\/keywords>/s) { + return split(/\n/, $1); + } + return undef; +} + +# provided a test number, return the it uses +sub tool { + my ($num) = @_; + open(my $fh, "<./data/test$num") || + print STDERR "can't open test$num\n"; + local $/; + my $c = <$fh>; + close($fh); + + # Match and extract the block + if ($c =~ /\n(.*?)<\/tool>/s) { + return split(/\n/, $1); + } + return undef; +} + +# return all the function names that are prototyped here, as a hash +sub unitprotos { + my ($num) = @_; + my %protos; + open(my $fh, "<$root/lib/unitprotos.h") || + print STDERR "can't open unitprotos.h\n"; + while(<$fh>) { + if(/^UNITTEST .*[* ]([a-z0-9_]+)\(/i) { + $protos{$1} = 1; + } + } + close($fh); + return %protos; +} + +# check if this unit test actually uses any internal symbols +sub checkunit { + my ($num, $protref) = @_; + open(my $fh, "<./unit/unit$num.c") || + print STDERR "can't open unit$num.c\n"; + my $private = 0; + while(<$fh>) { + # check all function calls + while(/([a-z0-9_]+)\(/cig) { + my $f = $1; + #print "Check ($num) $f\n"; + if($f =~ /^(Curl_|curlx_)/) { + $private = 1; + last; + } + elsif($$protref{$f}) { + # it uses prototyped one + $private = 1; + last; + } + } + if(/struct (Curl_|connectdata)/) { + $private = 1; + last; + } + elsif(/\bCURLPROTO_(WS|WSS|MQTTS|SOCKS)\b/) { + $private = 1; + last; + } + } + close($fh); + if(!$private) { + print "Unit test $num uses no private functions or structs\n"; + } + return !$private; +} + +sub checkxml { + my ($num) = @_; + open(my $fh, "<./data/test$num") || + print STDERR "can't open test$num\n"; + my $bad = 1; + while(<$fh>) { + if(/^<\?xml/) { + $bad = 0; + last; + } + print "data/test$num lacks tag\n"; + last; + } + close($fh); + return $bad; +} + +my $error; +my @l = libtests(); + +print "- Tests with source code in libtests/ have 'libtest' as \n"; +for my $t (@l) { + my $lt = 0; + my @k = keywords($t); + for my $w (@k) { + if($w eq "libtest") { + $lt = 1; + } + } + if(!$lt) { + print "Test $t in data/test$t lacks the 'libtest' keyword\n"; + $error++; + } +} + +print "- Tests with source code in tunit/ have 'tunittest' as \n"; +my @l = tunittests(); +for my $t (@l) { + my $lt = 0; + my @k = keywords($t); + for my $w (@k) { + if($w eq "tunittest") { + $lt = 1; + } + elsif($w eq "unittest") { + print "Test $t in data/test$t wrongly has the 'unittest' keyword\n"; + } + } + if(!$lt) { + print "Test $t in data/test$t lacks the 'tunittest' keyword\n"; + $error++; + } +} + +print "- Tests with source code in unit/ have 'unittest' as \n"; +my @l = unittests(); +for my $t (@l) { + my $lt = 0; + my @k = keywords($t); + for my $w (@k) { + if($w eq "unittest") { + $lt = 1; + } + elsif($w eq "tunittest") { + print "Test $t in data/test$t wrongly has the 'tunittest' keyword\n"; + } + } + if(!$lt) { + print "Test $t in data/test$t lacks the 'unittest' keyword\n"; + $error++; + } +} + +my @a = alltests(); +print "- Tests that set lib* in have 'libtest' as \n"; +for my $t (@a) { + my @k = tool($t); + for my $w (@k) { + if($w =~ /lib%TESTNUMBER/) { + print "test $t invokes default tool\n"; + $error++; + } + elsif($w =~ /lib(\d+)/) { + my $num = $1; + if($num == $t) { + print "test $t invokes default tool\n"; + $error++; + } + my $lt = 0; + + # get the keywords for test $num + my @k = keywords($t); + for my $w (@k) { + if($w eq "libtest") { + $lt = 1; + } + } + if(!$lt) { + print "Test $t in data/test$t lacks the 'libtest' keyword\n"; + $error++; + } + } + } +} + +print "- Tests that set tool* in have 'tunittest' as \n"; +for my $t (@a) { + my @k = tool($t); + for my $w (@k) { + if($w =~ /tool%TESTNUMBER/) { + print "test $t invokes default tool\n"; + $error++; + } + elsif($w =~ /tool(\d+)/) { + my $num = $1; + if($num == $t) { + print "test $t invokes default tool\n"; + $error++; + } + my $lt = 0; + + # get the keywords for test $num + my @k = keywords($num); + for my $w (@k) { + if($w eq "tunittest") { + $lt = 1; + } + } + if(!$lt) { + print "Test $t in data/test$t lacks the 'tunittest' keyword\n"; + $error++; + } + } + } +} + +print "- Tests that set unit* in have 'unittest' as \n"; +for my $t (@a) { + my @k = tool($t); + for my $w (@k) { + if($w =~ /unit%TESTNUMBER/) { + print "test $t invokes default tool\n"; + $error++; + } + elsif($w =~ /unit(\d+)/) { + my $num = $1; + if($num == $t) { + print "test $t invokes default tool\n"; + $error++; + } + my $lt = 0; + + # get the keywords for test $num + my @k = keywords($num); + for my $w (@k) { + if($w eq "unittest") { + $lt = 1; + } + } + if(!$lt) { + print "Test $t in data/test$t lacks the 'unittest' keyword\n"; + $error++; + } + } + } +} + +print "- Unit tests actually use private symbols\n"; +my @l = unittests(); +my %protos = unitprotos(); +for my $t (@l) { + $error += checkunit($t, \%protos); +} + +print "- All tests have tag on first line\n"; +for my $t (@a) { + $error += checkxml($t); +} + +exit $error; From 466c06cf706bd92ff77beb402934f7265183f8ff Mon Sep 17 00:00:00 2001 From: Filippo Tedeschi Date: Wed, 23 Sep 2026 10:23:22 +0200 Subject: [PATCH 217/481] urlapi: return NULL from curl_url_dup() when given a NULL handle Previously, passing a NULL pointer to curl_url_dup() caused a NULL pointer dereference crash on in->scheme inside the DUP() macro. Return NULL immediately when !in, matching curl_easy_duphandle() and preventing unexpected crashes when duplicating uninitialized or failed handles. Also verify this behavior in test 1560 and update documentation. Closes #23051 --- docs/libcurl/curl_url_dup.md | 3 ++- lib/urlapi.c | 7 ++++++- tests/libtest/lib1560.c | 3 +++ 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/docs/libcurl/curl_url_dup.md b/docs/libcurl/curl_url_dup.md index cfba5cc1b93e..620849444035 100644 --- a/docs/libcurl/curl_url_dup.md +++ b/docs/libcurl/curl_url_dup.md @@ -56,4 +56,5 @@ int main(void) # RETURN VALUE -Returns a pointer to a new `CURLU` handle or NULL if out of memory. +Returns a pointer to a new `CURLU` handle or NULL if out of memory or if +*inhandle* is NULL. diff --git a/lib/urlapi.c b/lib/urlapi.c index e64adb80277e..4f7aacdbf451 100644 --- a/lib/urlapi.c +++ b/lib/urlapi.c @@ -1433,7 +1433,12 @@ void curl_url_cleanup(CURLU *u) CURLU *curl_url_dup(const CURLU *in) { - struct Curl_URL *u = curlx_calloc(1, sizeof(struct Curl_URL)); + struct Curl_URL *u; + + if(!in) + return NULL; + + u = curlx_calloc(1, sizeof(struct Curl_URL)); if(u) { DUP(u, in, scheme); DUP(u, in, user); diff --git a/tests/libtest/lib1560.c b/tests/libtest/lib1560.c index f045474be3c8..65d243eff441 100644 --- a/tests/libtest/lib1560.c +++ b/tests/libtest/lib1560.c @@ -2423,6 +2423,9 @@ static int test_api_errors(void) if(rc != CURLUE_BAD_HANDLE) return 1; + if(curl_url_dup(NULL)) + return 4; + /* NULL part pointer */ rc = curl_url_get(u, CURLUPART_URL, NULL, 0); if(rc != CURLUE_BAD_PARTPOINTER) From 75f37b64aea51fa545a56f47d49f5acba112eff2 Mon Sep 17 00:00:00 2001 From: Stefan Eissing Date: Wed, 23 Sep 2026 12:46:57 +0200 Subject: [PATCH 218/481] cf-ngtcp2-proxy: check for stream close on rx win update The stream may already have been closed when checking for receive window updates. Check for that. Closes #23054 --- lib/vquic/cf-ngtcp2-proxy.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/vquic/cf-ngtcp2-proxy.c b/lib/vquic/cf-ngtcp2-proxy.c index 59e9228575bb..fad4c014571c 100644 --- a/lib/vquic/cf-ngtcp2-proxy.c +++ b/lib/vquic/cf-ngtcp2-proxy.c @@ -276,6 +276,8 @@ static void cf_h3_proxy_upd_rx_win(struct Curl_cfilter *cf, uint64_t cur_win, wanted_win = 0; size_t rx_buffered; + if(!stream) /* already closed */ + return; if(stream->rx_offset_max < stream->rx_offset) { DEBUGASSERT(0); return; From 3d8dfb77cfa48353e15583f8a358b83db7ebee59 Mon Sep 17 00:00:00 2001 From: Stefan Eissing Date: Wed, 23 Sep 2026 13:31:47 +0200 Subject: [PATCH 219/481] if2ip: check interface name case sensitive When iterating over the system list of interface names, check the name case preserving. Reported-by: Joshua Rogers Closes #23056 --- lib/if2ip.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/if2ip.c b/lib/if2ip.c index 9a9fbe7691d3..fee49ca3648b 100644 --- a/lib/if2ip.c +++ b/lib/if2ip.c @@ -110,7 +110,7 @@ if2ip_result_t Curl_if2ip(int af, for(iface = head; iface; iface = iface->ifa_next) { if(iface->ifa_addr) { if(iface->ifa_addr->sa_family == af) { - if(curl_strequal(iface->ifa_name, interf)) { + if(!strcmp(iface->ifa_name, interf)) { void *addr; char scope[12] = ""; char ipstr[64]; @@ -161,7 +161,7 @@ if2ip_result_t Curl_if2ip(int af, } } else if((res == IF2IP_NOT_FOUND) && - curl_strequal(iface->ifa_name, interf)) { + !strcmp(iface->ifa_name, interf)) { res = IF2IP_AF_NOT_SUPPORTED; } } From 636dab5daf1618a385ed87436a2524f776d0edfb Mon Sep 17 00:00:00 2001 From: Stefan Eissing Date: Wed, 23 Sep 2026 13:51:00 +0200 Subject: [PATCH 220/481] wolfssl: remove BIO destroy callback It never did anything, it is not necessary. Remove the code. Closes #23058 --- lib/vtls/wolfssl.c | 8 -------- 1 file changed, 8 deletions(-) diff --git a/lib/vtls/wolfssl.c b/lib/vtls/wolfssl.c index facac6f34c63..e17aa09a8863 100644 --- a/lib/vtls/wolfssl.c +++ b/lib/vtls/wolfssl.c @@ -230,13 +230,6 @@ static int wssl_bio_cf_create(WOLFSSL_BIO *bio) return 1; } -static int wssl_bio_cf_destroy(WOLFSSL_BIO *bio) -{ - if(!bio) - return 0; - return 1; -} - static long wssl_bio_cf_ctrl(WOLFSSL_BIO *bio, int cmd, long num, void *ptr) { struct Curl_cfilter *cf = wolfSSL_BIO_get_data(bio); @@ -373,7 +366,6 @@ static int wssl_bio_cf_init_methods(void) wolfSSL_BIO_meth_set_read(wssl_bio_cf_method, &wssl_bio_cf_in_read); wolfSSL_BIO_meth_set_ctrl(wssl_bio_cf_method, &wssl_bio_cf_ctrl); wolfSSL_BIO_meth_set_create(wssl_bio_cf_method, &wssl_bio_cf_create); - wolfSSL_BIO_meth_set_destroy(wssl_bio_cf_method, &wssl_bio_cf_destroy); return TRUE; /* fine */ } From 082503f1f623906eb00e57604b1c92ee8c69a2b5 Mon Sep 17 00:00:00 2001 From: Stefan Eissing Date: Wed, 23 Sep 2026 12:18:04 +0200 Subject: [PATCH 221/481] connection pool: use tbl and bsets Handle concurrent modifications to the connection pool, e.g. removing connections while iterating the pool/a bundle, by replacing the llists with a uint32_tbl and uint32_(sp)bsets. - Introduce a `uint32_t cpid` connection pool identifier assigned to connections when added to a pool. - Keep track if idle connections via their `cpid` - Track `data->state.last_cpid` for fast lookups of the last connection used by a transfer - Add reference counter to `cpool_bundle`s, so a bundle does not get destroyed while iterating it and the last connection gets removed from the bundle Closes #23053 --- lib/conncache.c | 657 +++++++++++++++++++++++------------------------ lib/conncache.h | 15 +- lib/connect.c | 22 +- lib/cshutdn.c | 2 +- lib/curl_share.c | 3 +- lib/curl_trc.c | 2 +- lib/easy.c | 8 +- lib/getinfo.c | 2 +- lib/multi.c | 8 +- lib/url.c | 4 +- lib/urldata.h | 9 +- 11 files changed, 351 insertions(+), 381 deletions(-) diff --git a/lib/conncache.c b/lib/conncache.c index 00ee24263fb9..6ffc5f5bcfdf 100644 --- a/lib/conncache.c +++ b/lib/conncache.c @@ -34,6 +34,7 @@ #include "conncache.h" #include "curl_share.h" #include "sigpipe.h" +#include "uint-spbset.h" #define CPOOL_IS_LOCKED(c) ((c) && (c)->locked) @@ -59,113 +60,127 @@ } \ } while(0) -/* A list of connections to the same destination. */ +/* A set of connection pool ids to the same destination. */ struct cpool_bundle { - struct Curl_llist conns; /* connections in the bundle */ + struct uint32_spbset ids; /* bitset of connection `pool_id`s */ + uint32_t refcount; }; -static struct cpool_bundle *cpool_bundle_create(void) +static void cpool_bundle_unlink(struct cpool_bundle **pbundle) { - struct cpool_bundle *bundle = curlx_calloc(1, sizeof(*bundle)); - if(bundle) - Curl_llist_init(&bundle->conns, NULL); - return bundle; -} - -static void cpool_bundle_destroy(struct cpool_bundle *bundle) -{ - DEBUGASSERT(!Curl_llist_count(&bundle->conns)); - curlx_free(bundle); + if(*pbundle) { + struct cpool_bundle *bundle = *pbundle; + + DEBUGASSERT(bundle->refcount); + *pbundle = NULL; + if(bundle->refcount) + bundle->refcount--; + if(!bundle->refcount) { + Curl_uint32_spbset_destroy(&bundle->ids); + curlx_free(bundle); + } + } } -/* Add a connection to a bundle */ -static void cpool_bundle_add(struct cpool_bundle *bundle, - struct connectdata *conn) +static void cpool_bundle_link(struct cpool_bundle **pdest, + struct cpool_bundle *src) { - DEBUGASSERT(!Curl_node_llist(&conn->cpool_node)); - Curl_llist_append(&bundle->conns, conn, &conn->cpool_node); - conn->bits.in_cpool = TRUE; + if(*pdest != src) { + cpool_bundle_unlink(pdest); + *pdest = src; + if(src) { + DEBUGASSERT(src->refcount < UINT32_MAX); + src->refcount++; + } + } } -/* Remove a connection from a bundle */ -static void cpool_bundle_remove(struct cpool_bundle *bundle, - struct connectdata *conn) +static struct cpool_bundle *cpool_create_bundle(void) { - (void)bundle; - DEBUGASSERT(Curl_node_llist(&conn->cpool_node) == &bundle->conns); - Curl_node_remove(&conn->cpool_node); - conn->bits.in_cpool = FALSE; -} + /* created with refcount == 1 */ + struct cpool_bundle *bundle = curlx_calloc(1, sizeof(*bundle)); + if(!bundle) + return NULL; -static void cpool_bundle_free_entry(void *freethis) -{ - cpool_bundle_destroy((struct cpool_bundle *)freethis); + Curl_uint32_spbset_init(&bundle->ids); + bundle->refcount = 1; + return bundle; } -void Curl_cpool_init(struct cpool *cpool, - struct Curl_share *share, - size_t size) +/* Add a connection to a bundle */ +static bool cpool_bundle_add_id(struct cpool *cpool, + struct cpool_bundle *bundle, + const char *destination, + uint32_t pool_id) { - Curl_hash_init(&cpool->dest2bundle, size, CURL_HASH_TYPE_BYTES, - cpool_bundle_free_entry); - - cpool->share = share; - cpool->initialized = TRUE; + if(!Curl_uint32_spbset_add(&bundle->ids, pool_id)) + return FALSE; + if(Curl_uint32_spbset_count(&bundle->ids) == 1) { + /* first one added, register bundle */ + struct cpool_bundle *new_ref = NULL; + cpool_bundle_link(&new_ref, bundle); + if(!Curl_hash_add(&cpool->dest2bundle, + destination, strlen(destination), new_ref)) { + cpool_bundle_unlink(&new_ref); + return FALSE; + } + } + return TRUE; } -/* Return the "first" connection in the pool or NULL. */ -static struct connectdata *cpool_get_first(struct cpool *cpool) +/* Remove a connection from a bundle */ +static void cpool_bundle_remove_id(struct cpool *cpool, + struct cpool_bundle *bundle, + const char *destination, + uint32_t pool_id) { - struct Curl_hash_iterator iter; - struct Curl_hash_element *he; - struct cpool_bundle *bundle; - struct Curl_llist_node *conn_node; - - Curl_hash_start_iterate(&cpool->dest2bundle, &iter); - for(he = Curl_hash_next_element(&iter); he; - he = Curl_hash_next_element(&iter)) { - bundle = he->ptr; - conn_node = Curl_llist_head(&bundle->conns); - if(conn_node) - return Curl_node_elem(conn_node); + Curl_uint32_spbset_remove(&bundle->ids, pool_id); + if(!Curl_uint32_spbset_count(&bundle->ids)) { + /* Now empty, unregister bundle */ + Curl_hash_delete(&cpool->dest2bundle, destination, strlen(destination)); } - return NULL; } -static struct cpool_bundle *cpool_find_bundle(struct cpool *cpool, - const char *destination) +static void cpool_bundle_hash_dtor(void *entry) { - return Curl_hash_pick( - &cpool->dest2bundle, destination, strlen(destination) + 1); + struct cpool_bundle *bundle = entry; + cpool_bundle_unlink(&bundle); } -static void cpool_remove_bundle(struct cpool *cpool, - const char *destination) +static struct cpool_bundle *cpool_get_bundle(struct cpool *cpool, + const char *destination) { - if(!cpool) - return; - Curl_hash_delete(&cpool->dest2bundle, destination, strlen(destination) + 1); + struct cpool_bundle *bundle = NULL; + cpool_bundle_link(&bundle, Curl_hash_pick( + &cpool->dest2bundle, destination, strlen(destination))); + return bundle; } static void cpool_remove_conn(struct cpool *cpool, struct connectdata *conn) { - struct Curl_llist *list = Curl_node_llist(&conn->cpool_node); + uint32_t cpid = conn->cpid; + DEBUGASSERT(cpool); - if(list) { - /* The connection is certainly in the pool, but where? */ - struct cpool_bundle *bundle = cpool_find_bundle(cpool, conn->destination); - if(bundle && (list == &bundle->conns)) { - cpool_bundle_remove(bundle, conn); - if(!Curl_llist_count(&bundle->conns)) - cpool_remove_bundle(cpool, conn->destination); - conn->bits.in_cpool = FALSE; - cpool->num_conn--; - } - else { - /* Should have been in the bundle list */ - DEBUGASSERT(NULL); + if(cpid != UINT32_MAX) { + struct cpool_bundle *bundle; + +#ifdef DEBUGBUILD + { + struct connectdata *pooled_conn = + Curl_uint32_tbl_get(&cpool->conns, cpid); + DEBUGASSERT(conn == pooled_conn); } +#endif + Curl_uint32_tbl_remove(&cpool->conns, cpid); + Curl_uint32_bset_remove(&cpool->idles, cpid); + conn->cpid = UINT32_MAX; + + bundle = cpool_get_bundle(cpool, conn->destination); + DEBUGASSERT(bundle); + if(bundle) + cpool_bundle_remove_id(cpool, bundle, conn->destination, cpid); + cpool_bundle_unlink(&bundle); } } @@ -180,7 +195,7 @@ static void cpool_discard_conn(struct cpool *cpool, DEBUGASSERT(data); DEBUGASSERT(!data->conn); DEBUGASSERT(cpool); - DEBUGASSERT(!conn->bits.in_cpool); + DEBUGASSERT(conn->cpid == UINT32_MAX); admin = Curl_get_admin(data); /* @@ -217,38 +232,59 @@ static void cpool_discard_conn(struct cpool *cpool, else { struct Curl_multi *multi = data->multi; size_t max_shutdowns = multi->max_total_connections; - if(cpool->num_conn < max_shutdowns) - max_shutdowns -= cpool->num_conn; + uint32_t num_conns = Curl_uint32_tbl_count(&cpool->conns); + + if(num_conns < max_shutdowns) + max_shutdowns -= num_conns; else if(max_shutdowns) max_shutdowns = 1; else /* no connection limit set, let's restrict growth nevertheless */ - max_shutdowns = CURLMAX(cpool->num_conn / 4, 128); + max_shutdowns = CURLMAX(num_conns / 4, 128); Curl_cshutdn_add(&multi->cshutdn, multi, conn, max_shutdowns); } } +void Curl_cpool_init(struct cpool *cpool, + struct Curl_share *share, + size_t size) +{ + Curl_uint32_tbl_init(&cpool->conns); + Curl_uint32_bset_init(&cpool->idles); + Curl_hash_init(&cpool->dest2bundle, size, CURL_HASH_TYPE_BYTES, + cpool_bundle_hash_dtor); + cpool->share = share; + cpool->initialized = TRUE; + cpool->in_shutdown = FALSE; + cpool->locked = FALSE; +} + void Curl_cpool_destroy(struct cpool *cpool, struct Curl_easy *admin) { if(cpool && cpool->initialized && admin) { - struct connectdata *conn; - struct Curl_sigpipe_ctx pipe_ctx; + void *entry; + uint32_t cpid; - CURL_TRC_M(admin, "%s[CPOOL] destroy, %zu connections", - cpool->share ? "[SHARE] " : "", cpool->num_conn); + CURL_TRC_M(admin, "%s[CPOOL] destroy, %u connections", + cpool->share ? "[SHARE] " : "", + Curl_uint32_tbl_count(&cpool->conns)); /* Move all connections to the shutdown list */ - sigpipe_init(&pipe_ctx); CPOOL_LOCK(cpool, admin); - conn = cpool_get_first(cpool); - if(conn) + if(Curl_uint32_tbl_first(&cpool->conns, &cpid, &entry)) { + struct Curl_sigpipe_ctx pipe_ctx; + + sigpipe_init(&pipe_ctx); sigpipe_apply(admin, &pipe_ctx); - while(conn) { - cpool_remove_conn(cpool, conn); - cpool_discard_conn(cpool, admin, conn, FALSE); - conn = cpool_get_first(cpool); + do { + struct connectdata *conn = entry; + cpool_remove_conn(cpool, conn); + cpool_discard_conn(cpool, admin, conn, FALSE); + } while(Curl_uint32_tbl_next(&cpool->conns, cpid, &cpid, &entry)); + sigpipe_restore(&pipe_ctx); } CPOOL_UNLOCK(cpool, admin); - sigpipe_restore(&pipe_ctx); Curl_hash_destroy(&cpool->dest2bundle); + Curl_uint32_bset_destroy(&cpool->idles); + Curl_uint32_tbl_destroy(&cpool->conns); } } @@ -280,7 +316,8 @@ void Curl_cpool_xfer_init(struct Curl_easy *data) data->id = cpool->next_easy_id++; if(cpool->next_easy_id == CURL_OFF_T_MAX) cpool->next_easy_id = 0; - data->state.lastconnect_id = -1; + data->state.last_conn_id = -1; + data->state.last_cpid = UINT32_MAX; CPOOL_UNLOCK(cpool, data); } @@ -288,87 +325,74 @@ void Curl_cpool_xfer_init(struct Curl_easy *data) /* We should not get here, but in a non-debug build, do something */ DEBUGASSERT(0); data->id = 0; - data->state.lastconnect_id = -1; + data->state.last_conn_id = -1; + data->state.last_cpid = UINT32_MAX; } } -static struct cpool_bundle *cpool_add_bundle(struct cpool *cpool, - const char *destination) +static bool cpool_conn_can_be_closed(struct connectdata *conn) { - struct cpool_bundle *bundle; - - bundle = cpool_bundle_create(); - if(!bundle) - return NULL; - - if(!Curl_hash_add(&cpool->dest2bundle, - destination, strlen(destination) + 1, bundle)) { - cpool_bundle_destroy(bundle); - return NULL; - } - return bundle; + /* CONNECT_ONLY sockets remain in use by the application. */ + return (conn && !CONN_INUSE(conn) && !conn->bits.connect_only); } static struct connectdata *cpool_bundle_get_oldest_idle( + struct cpool *cpool, struct cpool_bundle *bundle, const struct curltime *pnow) { - struct Curl_llist_node *curr; - struct connectdata *oldest_idle = NULL; + uint32_t cpid, oldest_id = UINT32_MAX; timediff_t unused_ms; timediff_t oldest_ms = -1; - struct connectdata *conn; - - curr = Curl_llist_head(&bundle->conns); - while(curr) { - conn = Curl_node_elem(curr); - - /* CONNECT_ONLY sockets remain in use by the application. */ - if(!CONN_INUSE(conn) && !conn->bits.close && !conn->bits.connect_only) { - /* Set higher score for the age passed since the connection was used */ - unused_ms = curlx_ptimediff_ms(pnow, &conn->created) - conn->lastused_ms; - if(unused_ms > oldest_ms) { - oldest_ms = unused_ms; - oldest_idle = conn; + + if(Curl_uint32_spbset_first(&bundle->ids, &cpid)) { + do { + struct connectdata *conn = Curl_uint32_tbl_get(&cpool->conns, cpid); + if(cpool_conn_can_be_closed(conn)) { + /* Set higher score for the age passed since the connection was used */ + unused_ms = + curlx_ptimediff_ms(pnow, &conn->created) - conn->lastused_ms; + if(unused_ms > oldest_ms) { + oldest_ms = unused_ms; + oldest_id = cpid; + } } - } - curr = Curl_node_next(curr); + else if(!conn) { + DEBUGASSERT(0); + Curl_uint32_spbset_remove(&bundle->ids, cpid); + } + } while(Curl_uint32_spbset_next(&bundle->ids, cpid, &cpid)); } - return oldest_idle; + return Curl_uint32_tbl_get(&cpool->conns, oldest_id); } static struct connectdata *cpool_get_oldest_idle(struct cpool *cpool, const struct curltime *pnow, timediff_t min_age_ms) { - struct Curl_hash_iterator iter; - struct Curl_llist_node *curr; - struct Curl_hash_element *he; - struct cpool_bundle *bundle; - struct connectdata *oldest_idle = NULL; + uint32_t cpid, oldest_id = UINT32_MAX; timediff_t oldest_idle_ms = -1; timediff_t idle_ms; - Curl_hash_start_iterate(&cpool->dest2bundle, &iter); - - for(he = Curl_hash_next_element(&iter); he; - he = Curl_hash_next_element(&iter)) { - struct connectdata *conn; - bundle = he->ptr; - - for(curr = Curl_llist_head(&bundle->conns); curr; - curr = Curl_node_next(curr)) { - conn = Curl_node_elem(curr); - if(CONN_INUSE(conn) || conn->bits.close || conn->bits.connect_only) - continue; - idle_ms = curlx_ptimediff_ms(pnow, &conn->created) - conn->lastused_ms; - if((idle_ms >= min_age_ms) && (idle_ms > oldest_idle_ms)) { - oldest_idle_ms = idle_ms; - oldest_idle = conn; + /* Iterate over all connections that once were idle. They may + * no longer be, in which case we remove them from the set again. */ + if(Curl_uint32_bset_first(&cpool->idles, &cpid)) { + do { + struct connectdata *conn = Curl_uint32_tbl_get(&cpool->conns, cpid); + if(cpool_conn_can_be_closed(conn)) { + idle_ms = curlx_ptimediff_ms(pnow, &conn->created) - conn->lastused_ms; + if((idle_ms >= min_age_ms) && (idle_ms > oldest_idle_ms)) { + oldest_idle_ms = idle_ms; + oldest_id = cpid; + } } - } + else { + DEBUGASSERT(conn); + Curl_uint32_bset_remove(&cpool->idles, cpid); + } + } while(Curl_uint32_bset_next(&cpool->idles, cpid, &cpid)); } - return oldest_idle; + return Curl_uint32_tbl_get(&cpool->conns, oldest_id); } static void cpool_conn_close(struct cpool *cpool, @@ -399,9 +423,9 @@ static void cpool_conn_close(struct cpool *cpool, if(do_lock) CPOOL_LOCK(cpool, admin); - if(conn->bits.in_cpool) { + if(conn->cpid != UINT32_MAX) { cpool_remove_conn(cpool, conn); - DEBUGASSERT(!conn->bits.in_cpool); + DEBUGASSERT(conn->cpid == UINT32_MAX); } /* treat the connection as aborted in CONNECT_ONLY situations, @@ -457,7 +481,7 @@ static int cpool_check_limits(struct Curl_easy *data, uint32_t max_total, struct connectdata *to_add, uint32_t max_host, - struct cpool_bundle **pbundle, + struct cpool_bundle *bundle, const struct curltime *pnow) { struct cpool *cpool = cpool_get_instance(data); @@ -476,13 +500,13 @@ static int cpool_check_limits(struct Curl_easy *data, * in shutdown and, if that does not lower it, evict idle connections * from the pool. */ admin = Curl_get_admin(data); - if(*pbundle) { - live = Curl_llist_count(&(*pbundle)->conns); + if(bundle) { + live = Curl_uint32_spbset_count(&bundle->ids); if(live >= max_host) { size_t over = live - max_host + 1; - for(; over && *pbundle; --over) { + for(; over; --over) { struct connectdata *oldest_idle = - cpool_bundle_get_oldest_idle(*pbundle, pnow); + cpool_bundle_get_oldest_idle(cpool, bundle, pnow); if(!oldest_idle) break; /* disconnect the old conn and continue */ @@ -491,10 +515,8 @@ static int cpool_check_limits(struct Curl_easy *data, oldest_idle->connection_id, oldest_idle->destination, max_host); cpool_evict_conn(cpool, admin, oldest_idle); - /* bundle may get destroyed in disconnect, look it up again */ - *pbundle = cpool_find_bundle(cpool, to_add->destination); } - live = *pbundle ? Curl_llist_count(&(*pbundle)->conns) : 0; + live = Curl_uint32_spbset_count(&bundle->ids); } } if(cshutdn) { @@ -513,11 +535,12 @@ static int cpool_check_limits(struct Curl_easy *data, /* Internal transfers are outside the total limit */ if(max_total && !data->state.internal) { size_t shutdowns = 0; + uint32_t num_conns = Curl_uint32_tbl_count(&cpool->conns); if(!admin) admin = Curl_get_admin(data); - if(cpool->num_conn >= max_total) { - size_t over = cpool->num_conn - max_total + 1; + if(num_conns >= max_total) { + size_t over = num_conns - max_total + 1; for(; over; --over) { struct connectdata *oldest_idle = cpool_get_oldest_idle(cpool, pnow, 0); @@ -530,18 +553,17 @@ static int cpool_check_limits(struct Curl_easy *data, max_total); cpool_evict_conn(cpool, admin, oldest_idle); } - /* bundle might be gone due to evictions */ - if(*pbundle) - *pbundle = cpool_find_bundle(cpool, to_add->destination); } + + num_conns = Curl_uint32_tbl_count(&cpool->conns); if(cshutdn) { shutdowns = Curl_cshutdn_count(cshutdn); - if(shutdowns && (cpool->num_conn + shutdowns) >= max_total) { - size_t over = cpool->num_conn + shutdowns - max_total + 1; + if(shutdowns && (num_conns + shutdowns) >= max_total) { + size_t over = num_conns + shutdowns - max_total + 1; shutdowns -= Curl_cshutdn_close_oldest(cshutdn, admin, NULL, over); } } - if((cpool->num_conn + shutdowns) >= max_total) + if((num_conns + shutdowns) >= max_total) return CPOOL_LIMIT_TOTAL; } @@ -557,22 +579,23 @@ CURLcode Curl_cpool_add(struct Curl_easy *data, CURLcode result = CURLE_OK; struct cpool_bundle *bundle = NULL; struct cpool *cpool = cpool_get_instance(data); + uint32_t capacity, cpid; DEBUGASSERT(conn); DEBUGASSERT(cpool); if(!cpool) return CURLE_FAILED_INIT; + DEBUGASSERT(conn->cpid == UINT32_MAX); conn->created = *pnow; conn->shutdown.start_ms[FIRSTSOCKET] = conn->shutdown.start_ms[SECONDARYSOCKET] = -1; CPOOL_LOCK(cpool, data); - /* Find the bundle, should it exist, and check the limits */ - bundle = cpool_find_bundle(cpool, conn->destination); + bundle = cpool_get_bundle(cpool, conn->destination); - switch(cpool_check_limits(data, max_total, conn, max_host, &bundle, pnow)) { + switch(cpool_check_limits(data, max_total, conn, max_host, bundle, pnow)) { case CPOOL_LIMIT_DEST: infof(data, "No more connections allowed to host"); result = CURLE_NO_CONNECTION_AVAILABLE; @@ -586,21 +609,58 @@ CURLcode Curl_cpool_add(struct Curl_easy *data, break; } + /* We add the connection */ + capacity = Curl_uint32_tbl_capacity(&cpool->conns); + if(capacity == UINT32_MAX) { + infof(data, "No connections available, pool at max capacity"); + result = CURLE_NO_CONNECTION_AVAILABLE; + goto out; + } + if(Curl_uint32_tbl_count(&cpool->conns) >= capacity) { + uint32_t growth, ncapacity; + + growth = CURLMAX(capacity, 16); + if((UINT32_MAX - growth) >= capacity) + ncapacity = capacity + growth; + else + ncapacity = UINT32_MAX; + /* Grow bitset first, so table resize failure does not leave a too + * short bitset should that one fail. */ + if(Curl_uint32_bset_resize(&cpool->idles, ncapacity) || + Curl_uint32_tbl_resize(&cpool->conns, ncapacity)) { + result = CURLE_OUT_OF_MEMORY; + goto out; + } + } + if(!bundle) { - bundle = cpool_add_bundle(cpool, conn->destination); + bundle = cpool_create_bundle(); if(!bundle) { result = CURLE_OUT_OF_MEMORY; goto out; } } - cpool_bundle_add(bundle, conn); + if(!Curl_uint32_tbl_add(&cpool->conns, conn, &cpid)) { + DEBUGASSERT(0); + result = CURLE_OUT_OF_MEMORY; + goto out; + } + conn->cpid = cpid; + + if(!cpool_bundle_add_id(cpool, bundle, conn->destination, cpid)) { + Curl_uint32_tbl_remove(&cpool->conns, cpid); + conn->cpid = UINT32_MAX; + result = CURLE_OUT_OF_MEMORY; + goto out; + } + conn->connection_id = cpool->next_connection_id++; - cpool->num_conn++; CURL_TRC_M(data, "[CPOOL] added connection %" FMT_OFF_T ". " - "The cache now contains %zu members", - conn->connection_id, cpool->num_conn); + "The cache now contains %u members", + conn->connection_id, Curl_uint32_tbl_count(&cpool->conns)); out: + cpool_bundle_unlink(&bundle); CPOOL_UNLOCK(cpool, data); return result; @@ -625,31 +685,18 @@ static bool cpool_foreach(struct Curl_easy *data, struct Curl_easy *data, struct connectdata *conn, void *param)) { - struct Curl_hash_iterator iter; - struct Curl_hash_element *he; + void *entry; + uint32_t cpid; if(!cpool) return FALSE; - Curl_hash_start_iterate(&cpool->dest2bundle, &iter); - - he = Curl_hash_next_element(&iter); - while(he) { - struct Curl_llist_node *curr; - struct cpool_bundle *bundle = he->ptr; - he = Curl_hash_next_element(&iter); - - curr = Curl_llist_head(&bundle->conns); - while(curr) { - /* Yes, we need to update curr before calling func(), because func() - might decide to remove the connection */ - struct connectdata *conn = Curl_node_elem(curr); - curr = Curl_node_next(curr); - - if(func(cpool, data, conn, param) == 1) { + if(Curl_uint32_tbl_first(&cpool->conns, &cpid, &entry)) { + do { + struct connectdata *conn = entry; + if(conn && func(cpool, data, conn, param) == 1) return TRUE; - } - } + } while(Curl_uint32_tbl_next(&cpool->conns, cpid, &cpid, &entry)); } return FALSE; } @@ -667,17 +714,19 @@ static bool cpool_conn_now_idle(struct cpool *cpool, { struct connectdata *oldest_idle = NULL; struct Curl_easy *admin; - unsigned int maxconnects; + uint32_t maxconnects; bool kept = TRUE; timediff_t min_age_ms = 0; if(!data || !data->multi) return kept; + Curl_uint32_bset_add(&cpool->idles, conn->cpid); + if(!data->multi->maxconnects) { /* Attached transfers is a weak indicator of business. */ uint32_t attached = Curl_multi_xfers_attached(data->multi); - maxconnects = (attached <= UINT_MAX / 2) ? attached * 2 : UINT_MAX; + maxconnects = (attached <= UINT32_MAX / 2) ? attached * 2 : UINT32_MAX; /* We are guessing. So, only evict a "seemingly superfluous" connection * when has not been used for this long, */ min_age_ms = 1000; @@ -686,12 +735,12 @@ static bool cpool_conn_now_idle(struct cpool *cpool, maxconnects = data->multi->maxconnects; } - if(cpool && maxconnects) { + if(cpool && maxconnects && (maxconnects < UINT32_MAX)) { + uint32_t num_conns = Curl_uint32_tbl_count(&cpool->conns); admin = Curl_get_admin(data); - if(cpool->num_conn > maxconnects) { - infof(data, "Connection pool is full, closing the oldest of %zu/%u", - cpool->num_conn, maxconnects); - + if(num_conns > maxconnects) { + infof(data, "Connection pool is full, closing the oldest of %u/%u", + num_conns, maxconnects); oldest_idle = cpool_get_oldest_idle(cpool, pnow, min_age_ms); kept = (oldest_idle != conn); if(oldest_idle) { @@ -709,7 +758,6 @@ static void cpool_prune_dead(struct cpool *cpool, static bool cpool_find_act(struct cpool *cpool, struct Curl_easy *data, - struct cpool_bundle *bundle, struct connectdata *conn, cpool_match_result match) { @@ -717,16 +765,10 @@ static bool cpool_find_act(struct cpool *cpool, case CPOOL_MATCH_FOUND: return TRUE; case CPOOL_MATCH_TOO_OLD: - if(CONN_INUSE(conn)) + if(!cpool_conn_can_be_closed(conn)) return FALSE; FALLTHROUGH(); case CPOOL_MATCH_CLOSE: - DEBUGASSERT(conn->bits.in_cpool); - if(conn->bits.in_cpool) { - cpool_bundle_remove(bundle, conn); - conn->bits.in_cpool = FALSE; - cpool->num_conn--; - } cpool_conn_close(cpool, data, conn, FALSE); break; default: @@ -744,9 +786,7 @@ bool Curl_cpool_find(struct Curl_easy *data, void *userdata) { struct cpool *cpool = cpool_get_instance(data); - struct cpool_bundle *bundle; - struct Curl_llist_node *curr; - struct connectdata *conn; + struct cpool_bundle *bundle = NULL; bool found = FALSE; DEBUGASSERT(cpool); @@ -759,46 +799,49 @@ bool Curl_cpool_find(struct Curl_easy *data, if(prune_dead) cpool_prune_dead(cpool, data, pnow); - bundle = Curl_hash_pick(&cpool->dest2bundle, - destination, strlen(destination) + 1); - if(bundle) { - if(data->state.lastconnect_id >= 0) { - /* Try to find the previously used connection in this bundle - * and if it still matches, use that one. This assures that - * an authentication involving several requests is using - * the same connection again. */ - curr = Curl_llist_head(&bundle->conns); - while(!found && curr) { - conn = Curl_node_elem(curr); - curr = Curl_node_next(curr); - if(data->state.lastconnect_id != conn->connection_id) - continue; - found = cpool_find_act(cpool, data, bundle, conn, - conn_cb(conn, userdata)); - break; - } + if(data->state.last_cpid != UINT32_MAX) { + struct connectdata *conn = + Curl_uint32_tbl_get(&cpool->conns, data->state.last_cpid); + if(conn && (data->state.last_conn_id == conn->connection_id)) { + /* Indeed the last connection used by `data` still exists. + * Check that first, so that connection use gets "sticky". */ + found = cpool_find_act(cpool, data, conn, conn_cb(conn, userdata)); + if(found) + goto out; } - - if(!found) { - curr = Curl_llist_head(&bundle->conns); - while(!found && curr) { - conn = Curl_node_elem(curr); - curr = Curl_node_next(curr); - /* Already tried a matching connection above. */ - if(data->state.lastconnect_id == conn->connection_id) - continue; - found = cpool_find_act(cpool, data, bundle, conn, - conn_cb(conn, userdata)); - } + else { + /* No longer found here. */ + data->state.last_conn_id = -1; + data->state.last_cpid = UINT32_MAX; } + } - if(!Curl_llist_count(&bundle->conns)) - cpool_remove_bundle(cpool, destination); + bundle = cpool_get_bundle(cpool, destination); + if(bundle) { + uint32_t cpid; + if(Curl_uint32_spbset_first(&bundle->ids, &cpid)) { + do { + struct connectdata *conn = Curl_uint32_tbl_get(&cpool->conns, cpid); + if(conn) { + if(data->state.last_conn_id != conn->connection_id) { + found = cpool_find_act(cpool, data, conn, conn_cb(conn, userdata)); + if(found) + goto out; + } + } + else { + DEBUGASSERT(0); + Curl_uint32_spbset_remove(&bundle->ids, cpid); + } + } while(Curl_uint32_spbset_next(&bundle->ids, cpid, &cpid)); + } } +out: if(done_cb) { found = done_cb(userdata); } + cpool_bundle_unlink(&bundle); CPOOL_UNLOCK(cpool, data); return found; } @@ -814,13 +857,12 @@ static int cpool_reap_dead_cb(struct cpool *cpool, { struct cpool_reaper_ctx *reaper = param; - if(!CONN_INUSE(conn)) { + if(cpool_conn_can_be_closed(conn)) { if(conn->bits.no_reuse || conn->bits.close || !Curl_cpool_conn_seems_healthy(conn, admin, reaper->pnow)) { /* terminate conn and stop the iteration */ reaper->reaped++; cpool_conn_close(cpool, admin, conn, FALSE); - return 1; } } return 0; /* continue iteration */ @@ -843,8 +885,7 @@ static void cpool_prune_dead(struct cpool *cpool, memset(&reaper, 0, sizeof(reaper)); reaper.pnow = pnow; - while(cpool_foreach(admin, cpool, &reaper, cpool_reap_dead_cb)) - ; + cpool_foreach(admin, cpool, &reaper, cpool_reap_dead_cb); cpool->last_cleanup = *pnow; } } @@ -867,9 +908,8 @@ static int conn_upkeep(struct cpool *cpool, result = Curl_conn_keep_alive(admin, conn); Curl_detach_connection(admin); - if(result && !CONN_INUSE(conn)) { + if(result && cpool_conn_can_be_closed(conn)) { cpool_conn_close(cpool, admin, conn, FALSE); - return 1; } } return 0; /* continue iteration */ @@ -884,45 +924,27 @@ CURLcode Curl_cpool_upkeep(struct Curl_easy *data) return CURLE_OK; CPOOL_LOCK(cpool, admin); - while(cpool_foreach(admin, cpool, NULL, conn_upkeep)) - ; + cpool_foreach(admin, cpool, NULL, conn_upkeep); CPOOL_UNLOCK(cpool, admin); return CURLE_OK; } -struct cpool_find_ctx { - curl_off_t id; - struct connectdata *conn; -}; - -static int cpool_find_conn(struct cpool *cpool, - struct Curl_easy *data, - struct connectdata *conn, void *param) -{ - struct cpool_find_ctx *fctx = param; - (void)cpool; - (void)data; - if(conn->connection_id == fctx->id) { - fctx->conn = conn; - return 1; - } - return 0; -} - -struct connectdata *Curl_cpool_get_conn(struct Curl_easy *data, - curl_off_t conn_id) +struct connectdata *Curl_cpool_get_last_conn(struct Curl_easy *data) { struct cpool *cpool = cpool_get_instance(data); - struct cpool_find_ctx fctx; + if(cpool && (data->state.last_cpid != UINT32_MAX)) { + struct Curl_easy *admin = Curl_get_admin(data); + struct connectdata *conn; - if(!cpool) - return NULL; - fctx.id = conn_id; - fctx.conn = NULL; - CPOOL_LOCK(cpool, data); - cpool_foreach(data, cpool, &fctx, cpool_find_conn); - CPOOL_UNLOCK(cpool, data); - return fctx.conn; + CPOOL_LOCK(cpool, admin); + conn = Curl_uint32_tbl_get(&cpool->conns, data->state.last_cpid); + CPOOL_UNLOCK(cpool, admin); + if(conn && (data->state.last_conn_id == conn->connection_id)) + return conn; + data->state.last_cpid = UINT32_MAX; + data->state.last_conn_id = -1; + } + return NULL; } void Curl_cpool_return(struct Curl_easy *data, @@ -949,8 +971,10 @@ void Curl_cpool_return(struct Curl_easy *data, conn->connection_id, conn->origin->user_hostname, conn->origin->port); } - else /* connection was removed from the cpool and destroyed. */ - data->state.lastconnect_id = -1; + else { /* connection was removed from the cpool and destroyed. */ + data->state.last_conn_id = -1; + data->state.last_cpid = UINT32_MAX; + } break; case CPOOL_DO_CLOSE: Curl_conn_close(data, conn, FALSE); @@ -963,26 +987,14 @@ void Curl_cpool_return(struct Curl_easy *data, CPOOL_UNLOCK(cpool, data); } -static int cpool_mark_stale(struct cpool *cpool, - struct Curl_easy *admin, - struct connectdata *conn, void *param) -{ - (void)cpool; - (void)admin; - (void)param; - conn->bits.no_reuse = TRUE; - return 0; -} - static int cpool_reap_no_reuse(struct cpool *cpool, struct Curl_easy *admin, struct connectdata *conn, void *param) { (void)param; - if(!CONN_INUSE(conn) && conn->bits.no_reuse) { + conn->bits.no_reuse = TRUE; + if(cpool_conn_can_be_closed(conn)) cpool_conn_close(cpool, admin, conn, FALSE); - return 1; - } return 0; /* continue iteration */ } @@ -990,9 +1002,7 @@ void Curl_cpool_nw_changed(struct cpool *cpool, struct Curl_easy *admin) { if(cpool && admin) { CPOOL_LOCK(cpool, admin); - cpool_foreach(admin, cpool, NULL, cpool_mark_stale); - while(cpool_foreach(admin, cpool, NULL, cpool_reap_no_reuse)) - ; + cpool_foreach(admin, cpool, NULL, cpool_reap_no_reuse); CPOOL_UNLOCK(cpool, admin); } } @@ -1038,7 +1048,7 @@ bool Curl_cpool_conn_seems_healthy(struct connectdata *conn, bool healthy = TRUE; DEBUGASSERT(!data->conn); - if(!CONN_INUSE(conn) && cpool_conn_too_old(data, conn, pnow)) + if(cpool_conn_can_be_closed(conn) && cpool_conn_too_old(data, conn, pnow)) return FALSE; if((curlx_ptimediff_ms(pnow, &conn->created) - conn->lastchecked_ms) < 1000) return TRUE; @@ -1055,8 +1065,8 @@ bool Curl_cpool_conn_seems_healthy(struct connectdata *conn, Curl_attach_connection(admin, conn, FALSE); healthy = Curl_conn_is_alive(admin, conn, &input_pending); Curl_detach_connection(admin); - if(healthy && input_pending && - !CONN_INUSE(conn) && !Curl_conn_is_multiplex(conn, FIRSTSOCKET)) { + if(healthy && input_pending && cpool_conn_can_be_closed(conn) && + !Curl_conn_is_multiplex(conn, FIRSTSOCKET)) { /* Non-multiplexed connections without attached transfers should * not have input pending. The input might be a TLS Notify Close, * for all we know. */ @@ -1085,40 +1095,3 @@ timediff_t Curl_cpool_conn_age_ms(struct Curl_easy *data, (void)data; return curlx_ptimediff_ms(pnow, &conn->created); } - -#if 0 -/* Useful for debugging the connection pool */ -void Curl_cpool_print(struct cpool *cpool) -{ - struct Curl_hash_iterator iter; - struct Curl_llist_node *curr; - struct Curl_hash_element *he; - - if(!cpool) - return; - - curl_mfprintf(stderr, "=Bundle cache=\n"); - - Curl_hash_start_iterate(cpool->dest2bundle, &iter); - - he = Curl_hash_next_element(&iter); - while(he) { - struct cpool_bundle *bundle; - struct connectdata *conn; - - bundle = he->ptr; - - curl_mfprintf(stderr, "%s -", he->key); - curr = Curl_llist_head(bundle->conns); - while(curr) { - conn = Curl_node_elem(curr); - - curl_mfprintf(stderr, " [%p %d]", (void *)conn, conn->refcount); - curr = Curl_node_next(curr); - } - curl_mfprintf(stderr, "\n"); - - he = Curl_hash_next_element(&iter); - } -} -#endif diff --git a/lib/conncache.h b/lib/conncache.h index cbe39438b723..caf240ad4e5b 100644 --- a/lib/conncache.h +++ b/lib/conncache.h @@ -24,6 +24,8 @@ * SPDX-License-Identifier: curl * ***************************************************************************/ +#include "uint-bset.h" +#include "uint-table.h" #include "curlx/timeval.h" struct connectdata; @@ -47,15 +49,16 @@ void Curl_conn_close(struct Curl_easy *data, bool aborted); struct cpool { - /* the pooled connections, bundled per destination */ - struct Curl_hash dest2bundle; - size_t num_conn; + struct uint32_tbl conns; /* connections added to this pool */ + struct uint32_bset idles; /* pool_ids of conns being idle */ + struct Curl_hash dest2bundle; /* conn destination sets */ curl_off_t next_connection_id; curl_off_t next_easy_id; struct curltime last_cleanup; struct Curl_share *share; /* != NULL if pool belongs to share */ BIT(locked); BIT(initialized); + BIT(in_shutdown); }; /* Get connection pool instance for data or NULL if none exists */ @@ -76,9 +79,9 @@ void Curl_cpool_destroy(struct cpool *cpool, * Assigns `data->id`. */ void Curl_cpool_xfer_init(struct Curl_easy *data); -/* Get the connection with the given id from `data`'s conn pool. */ -struct connectdata *Curl_cpool_get_conn(struct Curl_easy *data, - curl_off_t conn_id); +/* Get the connection last used by data, + * if there was one and it still exists. */ +struct connectdata *Curl_cpool_get_last_conn(struct Curl_easy *data); /* Add the connection to the pool. */ CURLcode Curl_cpool_add(struct Curl_easy *data, diff --git a/lib/connect.c b/lib/connect.c index 8d5b8cb82723..e20df00d5d8b 100644 --- a/lib/connect.c +++ b/lib/connect.c @@ -121,31 +121,15 @@ timediff_t Curl_timeleft_now_ms(struct Curl_easy *data, curl_socket_t Curl_getconnectinfo(struct Curl_easy *data, struct connectdata **connp) { - DEBUGASSERT(data); - /* this works for an easy handle: * - that has been used for curl_easy_perform() * - that is associated with a multi handle, and whose connection * was detached with CURLOPT_CONNECT_ONLY */ - if(data->state.lastconnect_id != -1) { - struct connectdata *conn; - - conn = Curl_cpool_get_conn(data, data->state.lastconnect_id); - if(!conn) { - data->state.lastconnect_id = -1; - if(connp) - *connp = NULL; - return CURL_SOCKET_BAD; - } - - if(connp) - *connp = conn; - return conn->sock[FIRSTSOCKET]; - } + struct connectdata *conn = Curl_cpool_get_last_conn(data); if(connp) - *connp = NULL; - return CURL_SOCKET_BAD; + *connp = conn; + return conn ? conn->sock[FIRSTSOCKET] : CURL_SOCKET_BAD; } void Curl_conncontrol(struct connectdata *conn, int ctrl) diff --git a/lib/cshutdn.c b/lib/cshutdn.c index c49d52c9c8e9..7c4628014d2e 100644 --- a/lib/cshutdn.c +++ b/lib/cshutdn.c @@ -247,7 +247,7 @@ void Curl_cshutdn_terminate(struct Curl_easy *admin, /* there must be a connection to close */ DEBUGASSERT(conn); /* it must be removed from the connection pool */ - DEBUGASSERT(!conn->bits.in_cpool); + DEBUGASSERT(conn->cpid == UINT32_MAX); /* the transfer must be detached from the connection */ DEBUGASSERT(admin && !admin->conn); DEBUGASSERT(!admin->mid); diff --git a/lib/curl_share.c b/lib/curl_share.c index d27c6fe16861..f457ab6e280d 100644 --- a/lib/curl_share.c +++ b/lib/curl_share.c @@ -424,7 +424,8 @@ CURLcode Curl_share_easy_unlink(struct Curl_easy *data) if((share->specifier & (1 << CURL_LOCK_DATA_CONNECT))) { if(data->conn) Curl_detach_connection(data); - data->state.lastconnect_id = -1; + data->state.last_conn_id = -1; + data->state.last_cpid = UINT32_MAX; } #if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_COOKIES) diff --git a/lib/curl_trc.c b/lib/curl_trc.c index 2bf81e811964..6c967151049b 100644 --- a/lib/curl_trc.c +++ b/lib/curl_trc.c @@ -92,7 +92,7 @@ static struct curl_trc_feat Curl_trc_feat_ids = { static size_t trc_print_ids(struct Curl_easy *data, char *buf, size_t maxlen) { - curl_off_t cid = data->state.lastconnect_id; + curl_off_t cid = data->state.last_conn_id; if(data->id >= 0) { if(cid >= 0) return curl_msnprintf(buf, maxlen, CURL_TRC_FMT_IDSDC, data->id, cid); diff --git a/lib/easy.c b/lib/easy.c index 115a5f28767d..5fc14657a649 100644 --- a/lib/easy.c +++ b/lib/easy.c @@ -1030,7 +1030,8 @@ CURL *curl_easy_duphandle(CURL *curl) Curl_netrc_init(&outcurl->state.netrc); /* the connection pool is setup on demand */ - outcurl->state.lastconnect_id = -1; + outcurl->state.last_conn_id = -1; + outcurl->state.last_cpid = UINT32_MAX; outcurl->id = -1; outcurl->mid = UINT32_MAX; outcurl->master_mid = UINT32_MAX; @@ -1148,7 +1149,8 @@ void curl_easy_reset(CURL *curl) if(CURL_EAPI_ENTER(&guard, curl, easy_reset, NULL)) { struct Curl_easy *data = curl; - data->state.lastconnect_id = -1; /* clear remembered connection id */ + data->state.last_conn_id = -1; /* clear remembered connection id */ + data->state.last_cpid = UINT32_MAX; Curl_req_hard_reset(&data->req, data); Curl_hash_clean(&data->meta_hash); @@ -1267,7 +1269,7 @@ static CURLcode easy_connection(struct Curl_easy *data, if(sfd == CURL_SOCKET_BAD) { failf(data, "Failed to get last socket used for connection #%" FMT_OFF_T, - data->state.lastconnect_id); + data->state.last_conn_id); return CURLE_UNSUPPORTED_PROTOCOL; } diff --git a/lib/getinfo.c b/lib/getinfo.c index eda62da5155d..f841ebeaea7a 100644 --- a/lib/getinfo.c +++ b/lib/getinfo.c @@ -500,7 +500,7 @@ static CURLcode getinfo_offt(struct Curl_easy *data, CURLINFO info, *param_offt = data->id; break; case CURLINFO_CONN_ID: - *param_offt = data->state.lastconnect_id; + *param_offt = data->state.last_conn_id; break; case CURLINFO_EARLYDATA_SENT_T: *param_offt = data->progress.earlydata_sent; diff --git a/lib/multi.c b/lib/multi.c index 17c0114d26f6..2e782affdf67 100644 --- a/lib/multi.c +++ b/lib/multi.c @@ -825,7 +825,7 @@ CURLMcode Curl_multi_remove_handle(struct Curl_multi *multi, if(data->set.connect_only) { if(data->multi_easy) { - if(data->state.lastconnect_id != -1) { + if(data->state.last_conn_id != -1) { /* Mark any connect-only connection for closure */ struct connectdata *conn; (void)Curl_getconnectinfo(data, &conn); @@ -943,8 +943,10 @@ void Curl_attach_connection(struct Curl_easy *data, DEBUGASSERT(conn); DEBUGASSERT(conn->attached_xfers < UINT32_MAX); data->conn = conn; - if(matched) - data->state.lastconnect_id = conn->connection_id; + if(matched) { + data->state.last_conn_id = conn->connection_id; + data->state.last_cpid = conn->cpid; + } else DEBUGASSERT(!data->mid); /* admin handle */ conn->attached_xfers++; diff --git a/lib/url.c b/lib/url.c index d0c59b0b084b..add577bbca0a 100644 --- a/lib/url.c +++ b/lib/url.c @@ -463,7 +463,8 @@ CURLcode Curl_open(struct Curl_easy **curl) data->magic = CURLEASY_MAGIC_NUMBER; /* most recent connection is not yet defined */ - data->state.lastconnect_id = -1; + data->state.last_conn_id = -1; + data->state.last_cpid = UINT32_MAX; /* and not assigned an id yet */ data->id = -1; data->mid = UINT32_MAX; @@ -1129,6 +1130,7 @@ static struct connectdata *allocate_conn(struct Curl_easy *data) conn->recv_idx = 0; /* default for receiving transfer data */ conn->send_idx = 0; /* default for sending transfer data */ conn->connection_id = -1; /* no ID */ + conn->cpid = UINT32_MAX; /* not in connection pool yet */ conn->attached_xfers = 0; conn->shutdown.start_ms[FIRSTSOCKET] = conn->shutdown.start_ms[SECONDARYSOCKET] = -1; diff --git a/lib/urldata.h b/lib/urldata.h index 4da67561f6f6..85c5b6badb0a 100644 --- a/lib/urldata.h +++ b/lib/urldata.h @@ -220,7 +220,6 @@ struct ConnectBits { BIT(no_reuse); /* connection should not be reused */ BIT(shutdown_handler); /* connection shutdown: handler shut down */ BIT(shutdown_filters); /* connection shutdown: filters shut down */ - BIT(in_cpool); /* connection is kept in a connection pool */ BIT(dns_resolved); /* DNS records for connection were resolved */ }; @@ -286,7 +285,6 @@ struct connectdata { * the connection is cleaned up (see Curl_hash_add2()).*/ struct Curl_hash meta_hash; - struct Curl_llist_node cpool_node; /* conncache lists */ struct Curl_llist_node cshutdn_node; /* cshutdn list */ char *destination; /* hostname+port, used in conncache */ @@ -357,6 +355,8 @@ struct connectdata { #define CONN_INUSE(c) (!!(c)->attached_xfers) uint32_t attached_xfers; /* # of attached easy handles */ + uint32_t cpid; /* connection pool id */ + #ifdef USE_IPV6 uint32_t scope_id; /* Scope id for IPv6 */ #endif @@ -527,7 +527,6 @@ struct urlpieces { }; struct UrlState { - curl_off_t lastconnect_id; /* The last assigned connection or -1 */ /* Origin of the initial (e.g. not followed) request of a transfer. Credentials from CURLOPT_* are only valid for this origin. Always set once a transfer starts searching for connections. */ @@ -630,6 +629,10 @@ struct UrlState { uint32_t rtsp_next_server_CSeq; /* the session's next server CSeq */ uint32_t rtsp_CSeq_recv; /* most recent CSeq received */ #endif + + curl_off_t last_conn_id; /* The last assigned connection or -1 */ + uint32_t last_cpid; /* last connection pool id used */ + #if defined(USE_HTTP2) || defined(USE_HTTP3) int weight; /* shallow copy of data->set */ #endif From d343e13bea76683876eb3643d28c7bd4e0407d72 Mon Sep 17 00:00:00 2001 From: Stefan Eissing Date: Wed, 23 Sep 2026 13:13:23 +0200 Subject: [PATCH 222/481] peer: preserve ipv6 props from "connect-to" When a "connect-to" only changes the port, preserve the ipv6 relevant properties like zone and scope. Reported-by: Joshua Rogers Closes #23055 --- lib/peer.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/lib/peer.c b/lib/peer.c index 577cedbb334b..26f9b9e41411 100644 --- a/lib/peer.c +++ b/lib/peer.c @@ -464,6 +464,7 @@ CURLcode Curl_peer_from_connect_to(struct Curl_easy *data, { struct peer_parse pp; const char *portstr = NULL; + bool port_switch = FALSE; CURLcode result; Curl_peer_unlink(ppeer); @@ -494,6 +495,7 @@ CURLcode Curl_peer_from_connect_to(struct Curl_easy *data, } if(!pp.host_user.len) { /* no hostname found, only port switch */ + port_switch = TRUE; pp.host_user.str = dest->user_hostname; pp.host_user.len = strlen(dest->user_hostname); } @@ -501,6 +503,8 @@ CURLcode Curl_peer_from_connect_to(struct Curl_easy *data, result = peer_parse_host(data, &pp, FALSE); if(result) goto out; + /* On a port switch, ipv6 must stay the same */ + DEBUGASSERT(!port_switch || ((bool)pp.ipv6 == (bool)dest->ipv6)); if(portstr && portstr[1]) { const char *p = portstr + 1; @@ -522,6 +526,13 @@ CURLcode Curl_peer_from_connect_to(struct Curl_easy *data, goto out; } #endif + if(pp.ipv6 && port_switch) { + if(dest->zoneid) { + pp.zoneid.str = dest->zoneid; + pp.zoneid.len = strlen(dest->zoneid); + } + pp.scopeid = dest->scopeid; + } result = peer_create(&pp, ppeer); CURL_TRC_M(data, "connect-to peer_create2 -> %d", (int)result); From 7f4cfe13b67c5b876d109fba04a2f18c05ef6922 Mon Sep 17 00:00:00 2001 From: Stefan Eissing Date: Wed, 23 Sep 2026 16:39:44 +0200 Subject: [PATCH 223/481] pytest: check server status without ssl verify Some tests configure the server with an "invald" certificate. The code that checks the startup/shutdown status of a server needs to use '--insecure' or will hang/give the wrong result. Closes #23064 --- tests/http/testenv/h2o.py | 8 ++++---- tests/http/testenv/nghttpx.py | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/http/testenv/h2o.py b/tests/http/testenv/h2o.py index 82ab16b76bfb..8b77a9ae2354 100644 --- a/tests/http/testenv/h2o.py +++ b/tests/http/testenv/h2o.py @@ -220,13 +220,13 @@ def wait_for_state( url = f"https://{self._domain}:{self._port}/" while datetime.now(timezone.utc) < try_until: if live: - r = curl.http_get( - url=url, extra_args=["--trace", "curl.trace", "--trace-time"] - ) + r = curl.http_get(url=url, extra_args=[ + '--trace', 'curl.trace', '--trace-time', '--insecure' + ]) if r.exit_code == 0: return True else: - r = curl.http_get(url=url) + r = curl.http_get(url=url, extra_args=['--insecure']) if r.exit_code != 0: return True time.sleep(0.1) diff --git a/tests/http/testenv/nghttpx.py b/tests/http/testenv/nghttpx.py index ba0cbe1a798e..296b6b4679c9 100644 --- a/tests/http/testenv/nghttpx.py +++ b/tests/http/testenv/nghttpx.py @@ -182,7 +182,7 @@ def wait_live(self, timeout: timedelta): while datetime.now(timezone.utc) < try_until: xargs = [ '--trace', 'curl.trace', '--trace-time', - '--connect-timeout', '1' + '--connect-timeout', '1', '--insecure' ] if self.port_is_quic: xargs.extend(['--http3-only']) @@ -330,7 +330,7 @@ def wait_dead(self, timeout: timedelta): try_until = datetime.now(timezone.utc) + timeout while datetime.now(timezone.utc) < try_until: check_url = f'https://{self.env.proxy_domain}:{self._port}/' - r = curl.http_get(url=check_url) + r = curl.http_get(url=check_url, extra_args=['--insecure']) if r.exit_code != 0: return True log.debug(f'waiting for nghttpx-fwd to stop responding: {r}') From ea00da992efb72e81119a568350ba39e572c9c34 Mon Sep 17 00:00:00 2001 From: Samuel Henrique Date: Tue, 22 Sep 2026 22:07:10 -0700 Subject: [PATCH 224/481] sws: fix connection-monitor for connections closed before a request sws updates its connection-monitor state only when it parses a request. Since 5232c1620897 it no longer clears the state after the first logged disconnect, so the state a test sets with connection-monitor remains after that test ends. If the next test that uses the same server closes a connection before sws has parsed any request of that test, sws logs [DISCONNECT] even if that test does not use connection-monitor. Tests 3231 and 3232 do that: they close a CONNECT_ONLY connection, which never sent anything, before the HTTP transfer sends its request. They fail with an unexpected [DISCONNECT] in server.input whenever the state is still enabled from a previous test, such as 3223 to 3226. With parallel test runners this depends on which runner picks up the test, so the failures look random. Most Debian package builds of 8.23.0-rc1 failed like this. To reproduce, run this in the tests directory: ./runtests.pl 3226 3231 When a connection closes while the test number is unset (DOCNUMBER_NOTHING), read it from server.cmd, as sws already does for requests without one. Then use the connection-monitor setting of that test, or disable connection monitoring if there is no test number. A test using connection-monitor now gets [DISCONNECT] logged for a connection closed before its first request, regardless of the previous test. Ref: #22902 Follow-up to 5232c1620897213db1781937c0bace0b7320474f #22159 Closes #23072 --- tests/server/sws.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/tests/server/sws.c b/tests/server/sws.c index 5587e50feaf7..e41d3d5eb993 100644 --- a/tests/server/sws.c +++ b/tests/server/sws.c @@ -2131,6 +2131,17 @@ static int test_sws(int argc, const char *argv[]) goto sws_cleanup; if(rc < 0) { + if(req->testno == DOCNUMBER_NOTHING) { + /* The test number is unknown here, so connmon may be left over + from a previous test case, for example when a client closes + a connection without sending anything. Use the setting of + the test case in server.cmd, or disable it without one. */ + req->connmon = FALSE; + parse_cmdfile(req); + if(req->testno != DOCNUMBER_NOTHING) + sws_parse_servercmd(req); + } + logmsg("====> Client disconnect %d", req->connmon); if(req->connmon) { From f5e43ea9231560ee8a02060edbeaf675dede753f Mon Sep 17 00:00:00 2001 From: Filippo Tedeschi Date: Wed, 23 Sep 2026 16:02:09 +0200 Subject: [PATCH 225/481] easy: validate buffer pointer in curl_easy_recv and curl_easy_send Return CURLE_BAD_FUNCTION_ARGUMENT if a positive length is provided with a NULL buffer pointer in curl_easy_recv() and curl_easy_send(). Add regression verification to test 556. Closes #23063 --- lib/easy.c | 4 ++-- tests/libtest/lib556.c | 10 ++++++++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/lib/easy.c b/lib/easy.c index 5fc14657a649..64797e8a9485 100644 --- a/lib/easy.c +++ b/lib/easy.c @@ -1306,7 +1306,7 @@ CURLcode curl_easy_recv(CURL *curl, void *buffer, size_t buflen, size_t *n) CURLcode result; if(CURL_EAPI_ENTER(&guard, curl, easy_recv, &result)) { - if(!n) { + if(!n || (buflen && !buffer)) { result = CURLE_BAD_FUNCTION_ARGUMENT; goto out; } @@ -1381,7 +1381,7 @@ CURLcode curl_easy_send(CURL *curl, const void *buffer, size_t buflen, struct Curl_easy *data = curl; size_t written = 0; - if(!n) { + if(!n || (buflen && !buffer)) { result = CURLE_BAD_FUNCTION_ARGUMENT; goto out; } diff --git a/tests/libtest/lib556.c b/tests/libtest/lib556.c index cb8bbabfad36..d03396d1f910 100644 --- a/tests/libtest/lib556.c +++ b/tests/libtest/lib556.c @@ -27,6 +27,7 @@ static CURLcode test_lib556(const char *URL) { CURLcode result; CURL *curl; + size_t ncheck = 0; int transfers = 0; if(curl_global_init(CURL_GLOBAL_ALL) != CURLE_OK) { @@ -45,6 +46,15 @@ static CURLcode test_lib556(const char *URL) easy_setopt(curl, CURLOPT_CONNECT_ONLY, 1L); easy_setopt(curl, CURLOPT_VERBOSE, 1L); + if(curl_easy_recv(NULL, NULL, 0, NULL) != CURLE_BAD_FUNCTION_ARGUMENT || + curl_easy_recv(curl, NULL, 10, &ncheck) != CURLE_BAD_FUNCTION_ARGUMENT || + curl_easy_send(NULL, NULL, 0, NULL) != CURLE_BAD_FUNCTION_ARGUMENT || + curl_easy_send(curl, NULL, 10, &ncheck) != CURLE_BAD_FUNCTION_ARGUMENT) { + curl_mfprintf(stderr, "curl_easy_recv/send argument validation failed\n"); + result = TEST_ERR_FAILURE; + goto test_cleanup; + } + again: result = curl_easy_perform(curl); From f31774b6820b3eb24c8a3c6240afbf92071fddff Mon Sep 17 00:00:00 2001 From: Daniel Stenberg Date: Thu, 24 Sep 2026 08:52:40 +0200 Subject: [PATCH 226/481] output-dir.md: expand on its behavior There is no attempt done to neutralize `../` sequences or similar from the produced name. Reported-by: bupt-Yy-young on github Fixes #23067 Closes #23073 --- docs/cmdline-opts/output-dir.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/cmdline-opts/output-dir.md b/docs/cmdline-opts/output-dir.md index 468ecc8a1176..4e63035698d7 100644 --- a/docs/cmdline-opts/output-dir.md +++ b/docs/cmdline-opts/output-dir.md @@ -12,6 +12,7 @@ See-also: - remote-header-name Example: - --output-dir "tmp" -O $URL + - --output-dir "tmp" -o filename.html $URL --- # `--output-dir` @@ -24,3 +25,7 @@ command line, up until the first --next. If the specified target directory does not exist, the operation fails unless --create-dirs is also used. + +The specified output directory is prepended to the destination filename that +would otherwise be used, including any directory component. No attempt is made +to neutralize `../` sequences or similar constructs in the resulting path. From 8fd0b95c291751cfcafe7cc6fbd1a649f1bd9200 Mon Sep 17 00:00:00 2001 From: Daniel Stenberg Date: Thu, 24 Sep 2026 09:33:01 +0200 Subject: [PATCH 227/481] mqtt: remaininglength check for 32-bit systems This could not happen in real life. Reported-by: bupt-Yy-young on github Fixes #23071 Closes #23074 --- lib/mqtt.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lib/mqtt.c b/lib/mqtt.c index 752ad370c013..fc1d4b0ac4e5 100644 --- a/lib/mqtt.c +++ b/lib/mqtt.c @@ -590,6 +590,14 @@ static CURLcode mqtt_publish(struct Curl_easy *data) if(result) goto fail; + /* silly check for silly analyzers. On 32-bit systems a payload length close + to 4GB can wrap, but it is impossible to have such a large buffer in + memory on those systems */ + if((SIZE_MAX - topiclen - 2) < payloadlen) { + result = CURLE_TOO_LARGE; + goto fail; + } + remaininglength = payloadlen + 2 + topiclen; encodelen = mqtt_encode_len(encodedbytes, remaininglength); if(remaininglength > (MAX_MQTT_MESSAGE_SIZE - encodelen - 1)) { From f46a841bad0d1202c581c460820f151c5705bc04 Mon Sep 17 00:00:00 2001 From: Stefan Eissing Date: Wed, 23 Sep 2026 14:32:29 +0200 Subject: [PATCH 228/481] gtls: no early return on verify fail When using gnutls in QUIC and peer verification fails, remove the early return so that sessions for this peer are removed from the ssl session cache. Reported-by: Joshua Rogers Closes #23061 --- lib/vquic/vquic-tls.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/vquic/vquic-tls.c b/lib/vquic/vquic-tls.c index 94f69efa9081..99c0553a283a 100644 --- a/lib/vquic/vquic-tls.c +++ b/lib/vquic/vquic-tls.c @@ -169,8 +169,6 @@ CURLcode Curl_vquic_tls_verify_peer(struct curl_tls_ctx *ctx, result = Curl_gtls_verifyserver( cf, data, ctx->gtls.session, conn_config, &data->set.ssl, peer, CURL_EASY_STR(data, STRING_SSL_PINNEDPUBLICKEY)); - if(result) - return result; #elif defined(USE_WOLFSSL) (void)data; if(conn_config->verifyhost) { From f228410f77a7326552fe2b655da68217109cb7f0 Mon Sep 17 00:00:00 2001 From: Daniel Stenberg Date: Thu, 24 Sep 2026 10:28:33 +0200 Subject: [PATCH 229/481] http: verify headers in fewer passes Scan the whole header only once looking for content instead of (at worst) three times. Closes #23075 --- lib/http.c | 39 +++++++++++++++++++++------------------ 1 file changed, 21 insertions(+), 18 deletions(-) diff --git a/lib/http.c b/lib/http.c index 2d8f44e1a627..b4a7f5534132 100644 --- a/lib/http.c +++ b/lib/http.c @@ -3863,18 +3863,24 @@ CURLcode Curl_verify_header(struct Curl_easy *data, const char *hd, size_t hdlen) { struct SingleRequest *k = &data->req; - const char *ptr = memchr(hd, 0x00, hdlen); - if(ptr) { - /* this is bad, bail out */ - failf(data, "Nul byte in header"); - return CURLE_WEIRD_SERVER_REPLY; - } - if(hdlen > 2) { - ptr = memchr(hd, '\r', hdlen - 2); - if(ptr) { - /* CR may only precede the LF, nothing else */ - failf(data, "Carriage return found in header"); + size_t i; + bool has_colon = FALSE; + for(i = 0; i < hdlen; i++) { + switch(hd[i]) { + case '\0': + /* this is bad, bail out */ + failf(data, "Nul byte in header"); return CURLE_WEIRD_SERVER_REPLY; + case '\r': + if(i < hdlen - 2) { + /* CR may only precede the LF, nothing else */ + failf(data, "Carriage return found in header"); + return CURLE_WEIRD_SERVER_REPLY; + } + break; + case ':': + has_colon = TRUE; + break; } } if(k->headerline < 2) @@ -3883,13 +3889,10 @@ CURLcode Curl_verify_header(struct Curl_easy *data, if(((hd[0] == ' ') || (hd[0] == '\t')) && k->headerline > 2) /* line folding, cannot happen on line 2 */ ; - else { - ptr = memchr(hd, ':', hdlen); - if(!ptr) { - /* this is bad, bail out */ - failf(data, "Header without colon"); - return CURLE_WEIRD_SERVER_REPLY; - } + else if(!has_colon) { + /* this is bad, bail out */ + failf(data, "Header without colon"); + return CURLE_WEIRD_SERVER_REPLY; } return CURLE_OK; } From aff6f9332aa673a8cec31cde0be9992e84f0162d Mon Sep 17 00:00:00 2001 From: Max Dymond Date: Wed, 23 Sep 2026 16:30:52 +0100 Subject: [PATCH 230/481] ci: allow 30 minutes for valgrind jobs Closes #23065 --- .github/workflows/linux.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index b7e0e6afe5a1..0665ecb673cd 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -66,7 +66,7 @@ jobs: name: ${{ matrix.build.generate && 'CM' || 'AM' }} ${{ matrix.build.name }} runs-on: ${{ matrix.build.image || 'ubuntu-latest' }} container: ${{ matrix.build.container }} - timeout-minutes: 15 + timeout-minutes: ${{ contains(matrix.build.install_packages, 'valgrind') && 30 || 15 }} env: MATRIX_BUILD: ${{ matrix.build.generate && 'cmake' || 'autotools' }} MATRIX_INSTALL_PACKAGES: '${{ matrix.build.install_packages }}' From 9f121f75a7757c6a74c60bec663d35076407b35e Mon Sep 17 00:00:00 2001 From: Stefan Eissing Date: Thu, 24 Sep 2026 12:01:51 +0200 Subject: [PATCH 231/481] cf-h1-proxy: reset chunk state on new request Reset the chunk state on a subsequent request. Add test to verify. Fixes #23069 Closes #23076 Reported-by: bupt-Yy-young on github --- lib/cf-h1-proxy.c | 1 + tests/data/Makefile.am | 1 + tests/data/test2824 | 96 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 98 insertions(+) create mode 100644 tests/data/test2824 diff --git a/lib/cf-h1-proxy.c b/lib/cf-h1-proxy.c index 3d0083a5c84b..0fc92182354a 100644 --- a/lib/cf-h1-proxy.c +++ b/lib/cf-h1-proxy.c @@ -112,6 +112,7 @@ static CURLcode tunnel_reinit(struct Curl_cfilter *cf, ts->close_connection = FALSE; ts->maybe_folded = FALSE; ts->leading_unfold = FALSE; + ts->chunked_encoding = FALSE; ts->nsent = 0; ts->headerlines = 0; return CURLE_OK; diff --git a/tests/data/Makefile.am b/tests/data/Makefile.am index 0ce48d4f1d1c..bf81b61842fd 100644 --- a/tests/data/Makefile.am +++ b/tests/data/Makefile.am @@ -2029,6 +2029,7 @@ TESTCASES = \ test2722 \ test2723 \ test2805 \ + test2824 \ test2885 \ test2918 \ test2924 \ diff --git a/tests/data/test2824 b/tests/data/test2824 new file mode 100644 index 000000000000..dbc06d5217a0 --- /dev/null +++ b/tests/data/test2824 @@ -0,0 +1,96 @@ + + + + +HTTP +HTTP GET +HTTP CONNECT +HTTP proxy +HTTP proxy Digest auth +chunked Transfer-Encoding + + + +# Server-side + + +connection-monitor + + +# this is returned first since we get no proxy-auth + +HTTP/1.1 407 Authorization Required to proxy me my dear swsbounce +Proxy-Authenticate: Digest realm="weirdorealm", nonce="12345" +Proxy-Connection: Keep-Alive +Transfer-Encoding: chunked + +20 +And you should ignore this data. +FA0 +%repeat[4000 x X]% +0 + + + +# then this is returned when we get proxy-auth + +HTTP/1.1 407 Authorization Required to proxy me my dear swsbounce +Proxy-Authenticate: Basic +Content-Length: 0 + + + + +HTTP/1.1 403 Denied +Content-Length: 0 + + + + + +# Client-side + + +http + + +SSL +proxy +digest +!SSPI + + +HTTP proxy CONNECT 2 407s, chunked-encoded and 0-length + + +http://test.remote.example.com.%TESTNUMBER:1024/path/%TESTNUMBER0003 --proxy http://%HOSTIP:%HTTPPORT --proxy-user testuser:testpass --proxy-anyauth --proxytunnel --connect-timeout 10 + + + +# Verify data after the test has been "shot" + + +CONNECT test.remote.example.com.%TESTNUMBER:1024 HTTP/1.1 +Host: test.remote.example.com.%TESTNUMBER:1024 +User-Agent: curl/%VERSION +Proxy-Connection: Keep-Alive + +CONNECT test.remote.example.com.%TESTNUMBER:1024 HTTP/1.1 +Host: test.remote.example.com.%TESTNUMBER:1024 +Proxy-Authorization: Digest username="testuser", realm="weirdorealm", nonce="12345", uri="test.remote.example.com.%TESTNUMBER:1024", response="9d235aa1a02467aca00179549f1d2a62" +User-Agent: curl/%VERSION +Proxy-Connection: Keep-Alive + +CONNECT test.remote.example.com.%TESTNUMBER:1024 HTTP/1.1 +Host: test.remote.example.com.%TESTNUMBER:1024 +Proxy-Authorization: Basic dGVzdHVzZXI6dGVzdHBhc3M= +User-Agent: curl/%VERSION +Proxy-Connection: Keep-Alive + +[DISCONNECT] + + +7 + + + From c1a1a2968e707a9ad2aab6f5c2bc281402adf9cf Mon Sep 17 00:00:00 2001 From: Stefan Eissing Date: Thu, 24 Sep 2026 12:53:00 +0200 Subject: [PATCH 232/481] cf-h1-proxy: stricter status line parsing Expect the HTTP status on line 1 and parse it from there only. Do not match later lines for a status. Add test1827 to verify. Change test750 exit code to 8 (Weird Server Reply) Fixes #23068 Reported-by: bupt-Yy-young on github Closes #23077 --- lib/cf-h1-proxy.c | 47 ++++++++++++++++++++++++++------------ tests/data/Makefile.am | 1 + tests/data/test1827 | 51 ++++++++++++++++++++++++++++++++++++++++++ tests/data/test750 | 4 ++-- 4 files changed, 87 insertions(+), 16 deletions(-) create mode 100644 tests/data/test1827 diff --git a/lib/cf-h1-proxy.c b/lib/cf-h1-proxy.c index 0fc92182354a..bf0cc4c92db5 100644 --- a/lib/cf-h1-proxy.c +++ b/lib/cf-h1-proxy.c @@ -303,6 +303,30 @@ static CURLcode send_CONNECT(struct Curl_cfilter *cf, return result; } +static CURLcode on_resp_status(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct h1_tunnel_state *ts, + const char *header) +{ + struct SingleRequest *k = &data->req; + + (void)ts; + if(!strncmp(header, "HTTP/1.", 7) && + ((header[7] == '0') || (header[7] == '1')) && + (header[8] == ' ') && + ISDIGIT(header[9]) && ISDIGIT(header[10]) && ISDIGIT(header[11]) && + !ISDIGIT(header[12])) { + /* store the HTTP code from the proxy */ + data->info.httpproxycode = k->httpcode = ((header[9] - '0') * 100) + + ((header[10] - '0') * 10) + (header[11] - '0'); + CURL_TRC_CF(data, cf, "CONNECT%s HTTP status %d", + h1_proxy_is_udp(cf) ? "-UDP" : "", k->httpcode); + return CURLE_OK; + } + failf(data, "Invalid response header"); + return CURLE_WEIRD_SERVER_REPLY; +} + static CURLcode on_resp_header(struct Curl_cfilter *cf, struct Curl_easy *data, struct h1_tunnel_state *ts, @@ -391,17 +415,6 @@ static CURLcode on_resp_header(struct Curl_cfilter *cf, is_udp ? "-UDP" : ""); ts->close_connection = TRUE; } - else if(!strncmp(header, "HTTP/1.", 7) && - ((header[7] == '0') || (header[7] == '1')) && - (header[8] == ' ') && - ISDIGIT(header[9]) && ISDIGIT(header[10]) && ISDIGIT(header[11]) && - !ISDIGIT(header[12])) { - /* store the HTTP code from the proxy */ - data->info.httpproxycode = k->httpcode = ((header[9] - '0') * 100) + - ((header[10] - '0') * 10) + (header[11] - '0'); - CURL_TRC_CF(data, cf, "CONNECT%s HTTP status %d", - is_udp ? "-UDP" : "", k->httpcode); - } return result; } @@ -442,8 +455,11 @@ static CURLcode single_header(struct Curl_cfilter *cf, if(ISNEWLINE(linep[0])) { /* end of response-headers from the proxy */ - - if((407 == k->httpcode) && !data->state.authproblem) { + if(ts->headerlines == 1) { + ts->keepon = KEEPON_DONE; + result = CURLE_WEIRD_SERVER_REPLY; + } + else if((407 == k->httpcode) && !data->state.authproblem) { /* If we get a 407 response code with content length when we have no auth problem, we must ignore the whole response-body */ @@ -472,7 +488,10 @@ static CURLcode single_header(struct Curl_cfilter *cf, return result; } - result = on_resp_header(cf, data, ts, linep); + if(ts->headerlines == 1) + result = on_resp_status(cf, data, ts, linep); + else + result = on_resp_header(cf, data, ts, linep); if(result) return result; diff --git a/tests/data/Makefile.am b/tests/data/Makefile.am index bf81b61842fd..47085a421b6b 100644 --- a/tests/data/Makefile.am +++ b/tests/data/Makefile.am @@ -1748,6 +1748,7 @@ TESTCASES = \ test1800 \ test1801 \ test1802 \ + test1827 \ test1847 \ test1848 \ test1849 \ diff --git a/tests/data/test1827 b/tests/data/test1827 new file mode 100644 index 000000000000..46c67db70425 --- /dev/null +++ b/tests/data/test1827 @@ -0,0 +1,51 @@ + + + + +HTTPS +HTTP GET +HTTP CONNECT +HTTP proxy + + +# Server-side + + +HTTP/1.1 407 Needs proxy authentication +HTTP/1.1 200 is not the answer: nonono +Server: test-server/fake swsclose yesyes +Proxy-Authenticate: Basic "oh please" + + + + +# Client-side + + +http + + +proxy + + +HTTP CONNECT with faked status line header + + +http://test.anything.really.com:%TESTNUMBER --proxy %HOSTIP:%HTTPPORT --proxytunnel + + + +# Verify data after the test has been "shot" + + +7 + + +CONNECT test.anything.really.com:%TESTNUMBER HTTP/1.1 +Host: test.anything.really.com:%TESTNUMBER +User-Agent: curl/%VERSION +Proxy-Connection: Keep-Alive + + + + diff --git a/tests/data/test750 b/tests/data/test750 index 03352ece66b9..ce7ac14b8006 100644 --- a/tests/data/test750 +++ b/tests/data/test750 @@ -49,10 +49,10 @@ Proxy-Connection: Keep-Alive -43 +8 -curl: (43) Invalid response header +curl: (8) Invalid response header From 6ee40bd5aa95c28e554ef8f203fc6d28c1397907 Mon Sep 17 00:00:00 2001 From: Daniel Stenberg Date: Thu, 24 Sep 2026 13:18:39 +0200 Subject: [PATCH 233/481] CURLOPT_POSTFIELDSIZE.md: improve wording Clarify that the size must not be larger than the buffer. Closes #23078 --- docs/libcurl/opts/CURLOPT_POSTFIELDSIZE.md | 10 ++++++---- docs/libcurl/opts/CURLOPT_POSTFIELDSIZE_LARGE.md | 10 ++++++---- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/docs/libcurl/opts/CURLOPT_POSTFIELDSIZE.md b/docs/libcurl/opts/CURLOPT_POSTFIELDSIZE.md index d45121b53f1d..3443a7d21d3f 100644 --- a/docs/libcurl/opts/CURLOPT_POSTFIELDSIZE.md +++ b/docs/libcurl/opts/CURLOPT_POSTFIELDSIZE.md @@ -28,12 +28,14 @@ CURLcode curl_easy_setopt(CURL *handle, CURLOPT_POSTFIELDSIZE, long size); # DESCRIPTION -If you want to post static data to the server without having libcurl do a -strlen() to measure the data size, this option must be used. When this option -is used you can post fully binary data, which otherwise is likely to fail. If -this size is set to -1, libcurl uses strlen() to get the size or relies on the +Set the *size* of the data buffer to POST to prevent libcurl from doing a +strlen() to figure out the data size. When this option is used you can post +fully binary data, which otherwise is likely to fail. If this size is set to +-1, libcurl uses strlen() to get the size or relies on the CURLOPT_READFUNCTION(3) (if used) to signal the end of data. +You *must* make sure that the set size is not larger than the provided buffer. + If you post more than 2GB, use CURLOPT_POSTFIELDSIZE_LARGE(3). # DEFAULT diff --git a/docs/libcurl/opts/CURLOPT_POSTFIELDSIZE_LARGE.md b/docs/libcurl/opts/CURLOPT_POSTFIELDSIZE_LARGE.md index 9f2fcb2f9715..2dc710f56d97 100644 --- a/docs/libcurl/opts/CURLOPT_POSTFIELDSIZE_LARGE.md +++ b/docs/libcurl/opts/CURLOPT_POSTFIELDSIZE_LARGE.md @@ -30,12 +30,14 @@ CURLcode curl_easy_setopt(CURL *handle, CURLOPT_POSTFIELDSIZE_LARGE, # DESCRIPTION -If you want to post static data to the server without having libcurl do a -strlen() to measure the data size, this option must be used. When this option -is used you can post fully binary data, which otherwise is likely to fail. If -this size is set to -1, libcurl uses strlen() to get the size or relies on the +Set the *size* of the data buffer to POST to prevent libcurl from doing a +strlen() to figure out the data size. When this option is used you can post +fully binary data, which otherwise is likely to fail. If this size is set to +-1, libcurl uses strlen() to get the size or relies on the CURLOPT_READFUNCTION(3) (if used) to signal the end of data. +You *must* make sure that the set size is not larger than the provided buffer. + # DEFAULT -1 From 064cdf53d9645fba1fb6724394fa15ec3c8ea27d Mon Sep 17 00:00:00 2001 From: Andreas Westin Date: Thu, 24 Sep 2026 14:27:59 +0200 Subject: [PATCH 234/481] libssh: work with empty priv_key When no priv_key is configured and the default key file does not exsits, priv_key is set to "" which, when set in libssh, leads to an error. Only set priv_key when it is neither NULL nor the empty string. Fixes #23080 Closes #23082 --- lib/vssh/libssh.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/vssh/libssh.c b/lib/vssh/libssh.c index 276c7d225924..6516933cf427 100644 --- a/lib/vssh/libssh.c +++ b/lib/vssh/libssh.c @@ -834,7 +834,8 @@ static int myssh_in_AUTHLIST(struct Curl_easy *data, /* For public key auth we need either the private key or CURLSSH_AUTH_AGENT. */ if((sshc->auth_methods & SSH_AUTH_METHOD_PUBLICKEY) && - (sshc->priv_key || (data->set.ssh_auth_types & CURLSSH_AUTH_AGENT))) { + ((sshc->priv_key && sshc->priv_key[0]) || + (data->set.ssh_auth_types & CURLSSH_AUTH_AGENT))) { myssh_to(data, sshc, SSH_AUTH_PKEY_INIT); infof(data, "Authentication using SSH public key file"); } @@ -864,7 +865,7 @@ static int myssh_in_AUTH_PKEY_INIT(struct Curl_easy *data, /* Two choices, (1) private key was given on CMD, * (2) use the "default" keys. */ - if(sshc->priv_key) { + if(sshc->priv_key && sshc->priv_key[0]) { if(sshc->pubkey && !sshc->passphrase) { rc = ssh_userauth_try_publickey(sshc->ssh_session, NULL, sshc->pubkey); if(rc == SSH_AUTH_AGAIN) From 92a7da5914e3ffd998a2cac8212d2b33522620a5 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 24 Sep 2026 10:01:05 +0000 Subject: [PATCH 235/481] GHA: update dependency pyrefly to v1.3.1 Closes #22955 --- .github/scripts/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/scripts/requirements.txt b/.github/scripts/requirements.txt index 75b32d076e59..fc537ed0c201 100644 --- a/.github/scripts/requirements.txt +++ b/.github/scripts/requirements.txt @@ -4,6 +4,6 @@ cmakelang==0.6.13 codespell==2.4.3 -pyrefly==1.3.0 +pyrefly==1.3.1 reuse==6.2.0 ruff==0.16.7 From efc5e67bda67b9573694e820cc8c1273f01ce937 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 24 Sep 2026 10:01:10 +0000 Subject: [PATCH 236/481] GHA: update dependency cloudflare/quiche to v0.30.0 Closes #22988 --- .github/workflows/http3-linux.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/http3-linux.yml b/.github/workflows/http3-linux.yml index 04af3374581d..886064ef2d22 100644 --- a/.github/workflows/http3-linux.yml +++ b/.github/workflows/http3-linux.yml @@ -55,7 +55,7 @@ env: OPENSSL_PREV_VERSION: 3.6.2 OPENSSL_PREV_SHA256: aaf51a1fe064384f811daeaeb4ec4dce7340ec8bd893027eee676af31e83a04f # renovate: datasource=github-tags depName=cloudflare/quiche versioning=semver registryUrl=https://github.com - QUICHE_VERSION: 0.29.3 + QUICHE_VERSION: 0.30.0 # renovate: datasource=github-tags depName=wolfSSL/wolfssl versioning=semver extractVersion=^v?(?.+)-stable$ registryUrl=https://github.com WOLFSSL_VERSION: 5.9.2 # renovate: datasource=github-tags depName=ngtcp2/nghttp3 versioning=semver registryUrl=https://github.com From bc7863a09786507b3c6b7a4fc421b8d4c9bf4d04 Mon Sep 17 00:00:00 2001 From: Stefan Eissing Date: Thu, 24 Sep 2026 15:22:10 +0200 Subject: [PATCH 237/481] mbedtls: resend length check Turn the DEBUGASSERT into a runtime check, returning an error if a re-send after EAGAIN uses a shorter data length. Reported-by: Joshua Rogers Closes #23083 --- lib/vtls/mbedtls.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/vtls/mbedtls.c b/lib/vtls/mbedtls.c index 7de9f81502ab..e74be22ef21e 100644 --- a/lib/vtls/mbedtls.c +++ b/lib/vtls/mbedtls.c @@ -1283,7 +1283,12 @@ static CURLcode mbed_send(struct Curl_cfilter *cf, struct Curl_easy *data, * loses bytes, e.g. reporting all was sent but they were not. * Remember the blocked length and use that when set. */ if(backend->send_blocked) { - DEBUGASSERT(backend->send_blocked_len <= len); + if(backend->send_blocked_len > len) { + DEBUGASSERT(0); + failf(data, "re-send data shorter than previously"); + result = CURLE_BAD_FUNCTION_ARGUMENT; + goto out; + } CURL_TRC_CF(data, cf, "mbedtls_ssl_write(len=%zu) -> previously blocked " "on %zu bytes", len, backend->send_blocked_len); len = backend->send_blocked_len; @@ -1326,6 +1331,7 @@ static CURLcode mbed_send(struct Curl_cfilter *cf, struct Curl_easy *data, } } +out: CURL_TRC_CF(data, cf, "mbedtls_ssl_write(len=%zu) -> %d, %zu", len, (int)result, *pnwritten); return result; From 3b4f5bbba0357b84cd6fc37f5875a801260c5715 Mon Sep 17 00:00:00 2001 From: Stefan Eissing Date: Wed, 23 Sep 2026 14:08:04 +0200 Subject: [PATCH 238/481] peer: parsing a zoneid as number fix When interpreting a zoneid as a numeric scopeid, check that the complete zoneid is numeric and not only has a number prefix. Closes #23060 --- lib/peer.c | 4 ++-- tests/unit/unit2413.c | 51 ++++++++++++++++++++++++++++++++++++------- 2 files changed, 45 insertions(+), 10 deletions(-) diff --git a/lib/peer.c b/lib/peer.c index 26f9b9e41411..a6d9ebcd79ad 100644 --- a/lib/peer.c +++ b/lib/peer.c @@ -151,13 +151,13 @@ static CURLcode peer_create(struct peer_parse *pp, if(!peer->scopeid) { const char *p = peer->zoneid; curl_off_t scope; - if(!curlx_str_number(&p, &scope, UINT_MAX)) { + if(!curlx_str_number(&p, &scope, UINT_MAX) && !*p) { /* A plain number, use it directly as a scope id. */ peer->scopeid = (uint32_t)scope; } #ifdef HAVE_IF_NAMETOINDEX else { - /* Zone identifier is not numeric */ + /* Zone identifier is not fully numeric */ unsigned int idx = 0; idx = if_nametoindex(peer->zoneid); if(idx) { diff --git a/tests/unit/unit2413.c b/tests/unit/unit2413.c index 6a74943476ba..2e653c1309e6 100644 --- a/tests/unit/unit2413.c +++ b/tests/unit/unit2413.c @@ -24,6 +24,17 @@ #include "unitcheck.h" #include "urldata.h" +#ifdef HAVE_NET_IF_H +#include +#endif + +#if defined(HAVE_IF_NAMETOINDEX) && defined(USE_WINSOCK) +#if defined(__MINGW32__) && (__MINGW64_VERSION_MAJOR <= 5) +#include /* workaround for old mingw-w64 missing to include it */ +#endif +#include +#endif + static CURLcode test_create2413(const char *name, CURL *curl, const struct Curl_scheme *scheme, @@ -31,7 +42,8 @@ static CURLcode test_create2413(const char *name, uint16_t port, const char *exp_hostname, bool exp_ipv6, - const char *exp_zoneid) + const char *exp_zoneid, + int exp_scopeid) { struct Curl_peer *peer = NULL; CURLcode result; @@ -44,6 +56,10 @@ static CURLcode test_create2413(const char *name, } result = CURLE_FAILED_INIT; +#ifndef USE_IPV6 + (void)exp_scopeid; +#endif + if(peer->scheme != scheme) { curl_mfprintf(stderr, "%s: has wrong scheme", name); } @@ -67,6 +83,11 @@ static CURLcode test_create2413(const char *name, else if(!exp_zoneid && peer->zoneid) curl_mfprintf(stderr, "%s: zoneid=%s, expected nothing", name, peer->zoneid); +#ifdef USE_IPV6 + else if((exp_scopeid >= 0) && (peer->scopeid != (uint32_t)exp_scopeid)) + curl_mfprintf(stderr, "%s: scopeid=%u, expected %d", name, + peer->scopeid, exp_scopeid); +#endif else result = CURLE_OK; @@ -76,6 +97,16 @@ static CURLcode test_create2413(const char *name, return result; } +static uint32_t t2413_scopeid(const char *zone) +{ +#ifdef HAVE_IF_NAMETOINDEX + return (uint32_t)if_nametoindex(zone); +#else + (void)zone; + return 0; +#endif +} + static CURLcode test_unit2413(const char *arg) { UNITTEST_BEGIN_SIMPLE @@ -89,19 +120,23 @@ static CURLcode test_unit2413(const char *arg) } test_create2413("peer1", curl, &Curl_scheme_https, "test.curl.se", 1234, - "test.curl.se", FALSE, NULL); + "test.curl.se", FALSE, NULL, 0); test_create2413("peer2", curl, &Curl_scheme_https, "127.0.0.1", 1234, - "127.0.0.1", FALSE, NULL); + "127.0.0.1", FALSE, NULL, 0); test_create2413("peer3", curl, &Curl_scheme_https, "::1", 1234, - "::1", TRUE, NULL); + "::1", TRUE, NULL, 0); test_create2413("peer4", curl, &Curl_scheme_https, "[::1]", 1234, - "::1", TRUE, NULL); + "::1", TRUE, NULL, 0); test_create2413("peer5", curl, &Curl_scheme_https, "test.curl.se.", 1234, - "test.curl.se.", FALSE, NULL); + "test.curl.se.", FALSE, NULL, 0); test_create2413("peer6", curl, &Curl_scheme_https, "[::1%tada]", 1234, - "::1", TRUE, "tada"); + "::1", TRUE, "tada", t2413_scopeid("tada")); test_create2413("peer7", curl, &Curl_scheme_https, "::1%tada", 1234, - "::1", TRUE, "tada"); + "::1", TRUE, "tada", t2413_scopeid("tada")); + test_create2413("peer8", curl, &Curl_scheme_https, "::1%123", 1234, + "::1", TRUE, "123", 123); + test_create2413("peer9", curl, &Curl_scheme_https, "::1%123x", 1234, + "::1", TRUE, "123x", t2413_scopeid("123x")); curl_easy_cleanup(curl); curl_global_cleanup(); From 6e5dee3fd011706cd561b2384df2d8a011dd31a9 Mon Sep 17 00:00:00 2001 From: Filippo Tedeschi Date: Wed, 23 Sep 2026 15:45:22 +0200 Subject: [PATCH 239/481] ssls: validate input arguments in import, export, and unpack Return CURLE_BAD_FUNCTION_ARGUMENT when NULL or zero-length buffer arguments are provided to curl_easy_ssls_import(), or when a NULL callback is passed to curl_easy_ssls_export(). Validate pointers and length in Curl_ssl_session_unpack() before dereferencing. Add unit test verifications in test1678. Closes #23062 --- lib/easy.c | 14 ++++++++++---- lib/vtls/vtls_scache.c | 2 +- lib/vtls/vtls_spack.c | 1 + tests/libtest/lib1678.c | 26 ++++++++++++++++++++++++++ 4 files changed, 38 insertions(+), 5 deletions(-) diff --git a/lib/easy.c b/lib/easy.c index 64797e8a9485..fc0041e74c4d 100644 --- a/lib/easy.c +++ b/lib/easy.c @@ -1418,8 +1418,11 @@ CURLcode curl_easy_ssls_import(CURL *curl, const char *session_key, CURLcode result; if(CURL_EAPI_ENTER(&guard, curl, easy_ssls_import, &result)) { - result = Curl_ssl_session_import((struct Curl_easy *)curl, session_key, - shmac, shmac_len, sdata, sdata_len); + if(!sdata || !sdata_len) + result = CURLE_BAD_FUNCTION_ARGUMENT; + else + result = Curl_ssl_session_import((struct Curl_easy *)curl, session_key, + shmac, shmac_len, sdata, sdata_len); } CURL_EAPI_LEAVE(&guard); return result; @@ -1443,8 +1446,11 @@ CURLcode curl_easy_ssls_export(CURL *curl, CURLcode result; if(CURL_EAPI_ENTER(&guard, curl, easy_ssls_export, &result)) { - result = Curl_ssl_session_export((struct Curl_easy *)curl, - export_fn, userptr); + if(!export_fn) + result = CURLE_BAD_FUNCTION_ARGUMENT; + else + result = Curl_ssl_session_export((struct Curl_easy *)curl, + export_fn, userptr); } CURL_EAPI_LEAVE(&guard); return result; diff --git a/lib/vtls/vtls_scache.c b/lib/vtls/vtls_scache.c index d9564c11ca88..3867ba16f570 100644 --- a/lib/vtls/vtls_scache.c +++ b/lib/vtls/vtls_scache.c @@ -1218,7 +1218,7 @@ CURLcode Curl_ssl_session_import(struct Curl_easy *data, struct Curl_ssl_session *s = NULL; CURLcode result; - if(!scache) { + if(!scache || !sdata || !sdata_len) { result = CURLE_BAD_FUNCTION_ARGUMENT; goto out; } diff --git a/lib/vtls/vtls_spack.c b/lib/vtls/vtls_spack.c index d8e51c4fd118..b53b46b0a19d 100644 --- a/lib/vtls/vtls_spack.c +++ b/lib/vtls/vtls_spack.c @@ -247,6 +247,7 @@ CURLcode Curl_ssl_session_unpack(struct Curl_easy *data, size_t dlen; CURLcode result; + DEBUGASSERT(ps); DEBUGASSERT(buf); DEBUGASSERT(buflen); *ps = NULL; diff --git a/tests/libtest/lib1678.c b/tests/libtest/lib1678.c index a2d43c02e0e0..d754f32118d6 100644 --- a/tests/libtest/lib1678.c +++ b/tests/libtest/lib1678.c @@ -130,6 +130,32 @@ static CURLcode test_lib1678(const char *URL) if(result) goto test_cleanup; + if(curl_easy_ssls_import(NULL, "example.test:443", NULL, 0, packet, + packet_len) != CURLE_BAD_FUNCTION_ARGUMENT) { + result = CURLE_BAD_FUNCTION_ARGUMENT; + goto test_cleanup; + } + if(curl_easy_ssls_import(easy, "example.test:443", NULL, 0, NULL, + packet_len) != CURLE_BAD_FUNCTION_ARGUMENT) { + result = CURLE_BAD_FUNCTION_ARGUMENT; + goto test_cleanup; + } + if(curl_easy_ssls_import(easy, "example.test:443", NULL, 0, packet, 0) != + CURLE_BAD_FUNCTION_ARGUMENT) { + result = CURLE_BAD_FUNCTION_ARGUMENT; + goto test_cleanup; + } + if(curl_easy_ssls_export(NULL, NULL, NULL) != + CURLE_BAD_FUNCTION_ARGUMENT) { + result = CURLE_BAD_FUNCTION_ARGUMENT; + goto test_cleanup; + } + if(curl_easy_ssls_export(easy, NULL, NULL) != + CURLE_BAD_FUNCTION_ARGUMENT) { + result = CURLE_BAD_FUNCTION_ARGUMENT; + goto test_cleanup; + } + for(i = 0; i < T1678_IMPORT_COUNT; ++i) { result = curl_easy_ssls_import(easy, "example.test:443", From ed272699f36e5b6826373a84911a3ebfbcf82aac Mon Sep 17 00:00:00 2001 From: Viktor Szakats Date: Thu, 24 Sep 2026 23:09:19 +0200 Subject: [PATCH 240/481] GHA/macos: add Xcode 27, drop macos-14 The xcode-27 is incompatible with gcc-15, so use a macos-26 gcc-14 pairing instead. Number of combination jobs unchanged (9). Ref: https://github.com/curl/curl/issues/21236#issuecomment-5694891476 Follow-up to 1acdf3bd64b6344ff818621155acf4cf5c3d1f50 #18698 Closes #23087 --- .github/workflows/checksrc.yml | 1 + .github/workflows/macos.yml | 33 ++++++++++++++++----------------- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/.github/workflows/checksrc.yml b/.github/workflows/checksrc.yml index 1b82b70d23d5..f9f84c4a7237 100644 --- a/.github/workflows/checksrc.yml +++ b/.github/workflows/checksrc.yml @@ -231,6 +231,7 @@ jobs: --ignore matrix \ --ignore ubuntu-26.04 \ --ignore ubuntu-24.04-firewall \ + --ignore xcode-27 \ --ignore 'unexpected key "background"' \ --ignore 'unexpected key "cache-mode"' \ --ignore 'unexpected key "wait"' \ diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index 8c4a8d331240..19ee69eddea9 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -620,47 +620,46 @@ jobs: fail-fast: false matrix: # Sources: - # https://github.com/actions/runner-images/blob/main/images/macos/macos-14-arm64-Readme.md # https://github.com/actions/runner-images/blob/main/images/macos/macos-15-arm64-Readme.md # https://github.com/actions/runner-images/blob/main/images/macos/macos-26-arm64-Readme.md - compiler: [gcc-13, gcc-14, gcc-15, llvm@15, llvm@18, llvm@20, clang] - # Xcode support matrix as of 2025-10, with default macOS SDK versions and OS names, years: + # https://github.com/actions/runner-images/blob/main/images/macos/xcode-27-arm64-Readme.md + compiler: [gcc-13, gcc-14, gcc-15, llvm@18, llvm@20, clang] + # Xcode support matrix as of 2026-09, with default macOS SDK versions and OS names, years: # * = default Xcode on the runner. - # macos-14: 15.0.1, 15.1, 15.2, 15.3,*15.4 - # macos-15: 16.0, 16.1, 16.2, 16.3,*16.4, 26.0 - # macos-26: 16.4 *26.0 - # macOSSDK: 14.0, 14.2, 14.2, 14.4, 14.5, 15.0, 15.1, 15.2, 15.4, 15.5, 26.0 - # Sonoma (2023) Sequoia (2024) Tahoe (2025) + # macos-15: 16.0, 16.1, 16.2, 16.3,*16.4, 26.0 + # macos-26: 16.4,*26.6 + # xcode-27: *27.0 + # macOSSDK: 15.0, 15.1, 15.2, 15.4, 15.5, 26.x 27.x + # Sequoia (2024) Tahoe (2025) Golden Gate (2026) # https://github.com/actions/runner-images/tree/main/images/macos # https://en.wikipedia.org/wiki/MacOS_version_history - image: [macos-14, macos-15, macos-26] + image: [macos-15, macos-26, xcode-27] xcode: [''] # default Xcodes macos-version-min: [''] build: [autotools, cmake] exclude: # Combinations not covered by runner images: - - { image: macos-14, compiler: 'llvm@18' } - - { image: macos-14, compiler: 'llvm@20' } - - { image: macos-15, compiler: 'llvm@15' } - { image: macos-15, compiler: 'llvm@20' } - - { image: macos-26, compiler: 'llvm@15' } - { image: macos-26, compiler: 'llvm@18' } + - { image: xcode-27, compiler: 'llvm@18' } # Covered by the main workflow - { image: macos-15, compiler: 'gcc-13' } - { image: macos-15, compiler: 'llvm@18' } - { image: macos-15, compiler: 'clang' } # Reduce build combinations, by dropping less interesting ones - - { image: macos-26, compiler: 'gcc-13' } - - { compiler: 'gcc-14' , build: cmake } + - { image: macos-15, compiler: 'gcc-14' } + - { image: xcode-27, compiler: 'gcc-13' } + - { image: xcode-27, compiler: 'gcc-14' } + # incompatible combination + - { image: xcode-27, compiler: 'gcc-15' } # Reduce autotools to only one job that is also build with cmake - { compiler: 'gcc-13' , build: autotools } - { compiler: 'gcc-14' , build: autotools } - { compiler: 'gcc-15' , build: autotools } - - { compiler: 'llvm@15', build: autotools } - { compiler: 'llvm@18', build: autotools } - { compiler: 'llvm@20', build: autotools } - - { image: macos-14, build: autotools } - { image: macos-15, build: autotools } + - { image: macos-26, build: autotools } steps: - name: 'install autotools' if: ${{ matrix.build == 'autotools' }} From 8940c1436bb54cdbe304c8ed9004bc29a299ab9d Mon Sep 17 00:00:00 2001 From: Stefan Eissing Date: Thu, 24 Sep 2026 15:51:32 +0200 Subject: [PATCH 241/481] cw-out: recheck callbacks for each call A write function is allowed to invoked curl_easy_setopt and change itself. Get the write callback again after each chunk that has been written. Reported-by: Joshua Rogers Closes #23085 --- lib/cw-out.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/cw-out.c b/lib/cw-out.c index e451d2399374..9e85faccece4 100644 --- a/lib/cw-out.c +++ b/lib/cw-out.c @@ -267,6 +267,12 @@ static CURLcode cw_out_ptr_flush(struct cw_out_ctx *ctx, *pconsumed += nwritten; blen -= nwritten; buf += nwritten; + /* write functions/data may have been re-setopt'ed. sigh. */ + cw_get_writefunc(data, otype, &wcb, &wcb_data, &max_write, &min_write); + if(!wcb) { + *pconsumed += blen; + break; + } } } return CURLE_OK; From ee2959a98ee1253101b8e1d26d0fd3c1b9f61dac Mon Sep 17 00:00:00 2001 From: Stefan Eissing Date: Thu, 24 Sep 2026 16:24:28 +0200 Subject: [PATCH 242/481] httpsrr+ech: check target and port Before using the echconig from a HTTPS-RR, check if target and port match the peer it will be used for. Closes #23086 --- lib/vdns/httpsrr.c | 12 ++++++++++-- lib/vdns/httpsrr.h | 4 ++++ lib/vtls/openssl.c | 2 +- lib/vtls/rustls.c | 3 ++- lib/vtls/wolfssl.c | 2 +- 5 files changed, 18 insertions(+), 5 deletions(-) diff --git a/lib/vdns/httpsrr.c b/lib/vdns/httpsrr.c index 98a3c894964c..12a829978bfe 100644 --- a/lib/vdns/httpsrr.c +++ b/lib/vdns/httpsrr.c @@ -239,11 +239,19 @@ void Curl_httpsrr_destroy(struct Curl_https_rrinfo *rrinfo) bool Curl_httpsrr_applicable(struct Curl_easy *data, const struct Curl_https_rrinfo *rr) { - if(!data->conn || !rr) + if(!data->conn) + return FALSE; + return Curl_httpsrr_is_for_peer(data->conn->origin, rr); +} + +bool Curl_httpsrr_is_for_peer(struct Curl_peer *peer, + const struct Curl_https_rrinfo *rr) +{ + if(!peer || !rr) return FALSE; return (!rr->target || !rr->target[0] || (rr->target[0] == '.' && !rr->target[1])) && - (!rr->port_set || rr->port == data->conn->origin->port); + (!rr->port_set || rr->port == peer->port); } #ifdef USE_ARES diff --git a/lib/vdns/httpsrr.h b/lib/vdns/httpsrr.h index 8d6eed1c936c..1aa67b8d81b2 100644 --- a/lib/vdns/httpsrr.h +++ b/lib/vdns/httpsrr.h @@ -67,6 +67,10 @@ void Curl_httpsrr_destroy(struct Curl_https_rrinfo *rrinfo); bool Curl_httpsrr_applicable(struct Curl_easy *data, const struct Curl_https_rrinfo *rr); +/* TRUE if the record is applicable to the given peer. */ +bool Curl_httpsrr_is_for_peer(struct Curl_peer *peer, + const struct Curl_https_rrinfo *rr); + /* * Code points for DNS wire format SvcParams as per RFC 9460 */ diff --git a/lib/vtls/openssl.c b/lib/vtls/openssl.c index a874df997e4d..16fd9fe2bcdc 100644 --- a/lib/vtls/openssl.c +++ b/lib/vtls/openssl.c @@ -3527,7 +3527,7 @@ static CURLcode ossl_init_ech(struct ossl_ctx *octx, const struct Curl_https_rrinfo *rinfo = Curl_conn_dns_get_https(data, cf->sockindex, peer->origin); - if(rinfo && rinfo->echconfiglist) { + if(Curl_httpsrr_is_for_peer(peer->origin, rinfo) && rinfo->echconfiglist) { const unsigned char *ecl = rinfo->echconfiglist; size_t elen = rinfo->echconfiglist_len; diff --git a/lib/vtls/rustls.c b/lib/vtls/rustls.c index 8557c6953870..1a5cdd58f72a 100644 --- a/lib/vtls/rustls.c +++ b/lib/vtls/rustls.c @@ -984,7 +984,8 @@ init_config_builder_ech(struct Curl_easy *data, const struct Curl_https_rrinfo *rinfo = Curl_conn_dns_get_https(data, cf->sockindex, connssl->peer.origin); - if(!rinfo || !rinfo->echconfiglist) { + if(!Curl_httpsrr_is_for_peer(connssl->peer.origin, rinfo) || + !rinfo->echconfiglist) { failf(data, "rustls: ECH requested but no ECHConfig available"); result = CURLE_SSL_CONNECT_ERROR; goto cleanup; diff --git a/lib/vtls/wolfssl.c b/lib/vtls/wolfssl.c index e17aa09a8863..1071a1fb0b84 100644 --- a/lib/vtls/wolfssl.c +++ b/lib/vtls/wolfssl.c @@ -1277,7 +1277,7 @@ static CURLcode wssl_init_ech(struct wssl_ctx *wctx, const struct Curl_https_rrinfo *rinfo = Curl_conn_dns_get_https(data, cf->sockindex, peer->origin); - if(rinfo && rinfo->echconfiglist) { + if(Curl_httpsrr_is_for_peer(peer->origin, rinfo) && rinfo->echconfiglist) { const unsigned char *ecl = rinfo->echconfiglist; size_t elen = rinfo->echconfiglist_len; From d745b13cad63a13ebd1926a402944fdfe0a0022f Mon Sep 17 00:00:00 2001 From: Daniel Stenberg Date: Thu, 24 Sep 2026 14:01:24 +0200 Subject: [PATCH 243/481] tool_operate: set stdin non-blocking unconditionally When doing TELNET:// transfers are read from stdin without having set an upload file, so set stdin non-blocking mode unconditionally to make it able to time-out correctly. Added to KNOWN_BUGS as not working on native Windows. Update test 1548 to sit waiting on an idle stdin with telnet properly. Closes #23081 --- docs/KNOWN_BUGS.md | 7 +++++++ src/tool_operate.c | 7 ++----- tests/data/test1548 | 4 ---- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/docs/KNOWN_BUGS.md b/docs/KNOWN_BUGS.md index 90dcdd9c48ec..66f0b220526d 100644 --- a/docs/KNOWN_BUGS.md +++ b/docs/KNOWN_BUGS.md @@ -439,6 +439,13 @@ then subsequently fails anyway if that was actually in use. [curl issue 8112](https://github.com/curl/curl/issues/8112) +## Stdin is not non-blocking on Windows + +The telnet handler reads data from stdin by default without needing to +specify that on the command line, but since Windows is special, reading +from stdin is not done in a non-blocking manner, meaning that curl might +block waiting for input without triggering the timeout. + # HTTP/2 ## HTTP/2 prior knowledge over proxy diff --git a/src/tool_operate.c b/src/tool_operate.c index b5b3d6ad8c92..7001860f7d6e 100644 --- a/src/tool_operate.c +++ b/src/tool_operate.c @@ -1221,11 +1221,6 @@ static void check_stdin_upload(struct OperationConfig *config, else per->infd = (int)f; #endif - if(curlx_nonblock((curl_socket_t)per->infd, TRUE) < 0) { - char errbuf[STRERROR_LEN]; - warnf("fcntl failed on fd=%d: %s", per->infd, - curlx_strerror(errno, errbuf, sizeof(errbuf))); - } } } @@ -2360,6 +2355,8 @@ static CURLcode run_all_transfers(CURLSH *share, bool orig_noprogress = (bool)global->noprogress; bool orig_isatty = (bool)global->isatty; struct per_transfer *per; + (void)curlx_nonblock((curl_socket_t)STDIN_FILENO, TRUE); + /* fail silently */ /* Time to actually do the transfers */ if(!result) { diff --git a/tests/data/test1548 b/tests/data/test1548 index fbc80cb190d5..42d1ceffcaac 100644 --- a/tests/data/test1548 +++ b/tests/data/test1548 @@ -23,10 +23,6 @@ telnet TELNET read stdin without upload file - -GET /we/want/%TESTNUMBER HTTP/1.0 - - -m 1 telnet://%HOSTIP:%HTTPPORT From d4dc5b4ce85831222467b38449c9bbd0cb366d8a Mon Sep 17 00:00:00 2001 From: Daniel Stenberg Date: Thu, 24 Sep 2026 23:44:52 +0200 Subject: [PATCH 244/481] ftplistparser: when wildcarding, skip files with slashes A real file name cannot contain a slash. Skip those. Reported-by: Fernando Gallardo Extend the LIST response in test 2805 to verify that it gets ignored. Closes #23089 --- lib/ftplistparser.c | 5 ++++- tests/directories.pm | 11 ++++++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/lib/ftplistparser.c b/lib/ftplistparser.c index 77f90c763d8a..fa721cc78d38 100644 --- a/lib/ftplistparser.c +++ b/lib/ftplistparser.c @@ -323,7 +323,10 @@ static CURLcode ftp_pl_insert_finfo(struct Curl_easy *data, compare = Curl_fnmatch; /* filter pattern-corresponding filenames */ - { + if(strchr(finfo->filename, '/')) + add = FALSE; + else { + /* a real file name cannot contain a slash */ struct Curl_mapi_guard guard; CURL_CBAPI_START(&guard, data, easy_fnmatch_data); if(compare(data->set.fnmatch_data, wc->pattern, finfo->filename) == 0) { diff --git a/tests/directories.pm b/tests/directories.pm index f868ead9cb34..7df2291c3314 100644 --- a/tests/directories.pm +++ b/tests/directories.pm @@ -187,9 +187,18 @@ my %file_percent_txt = ( 'perm' => "rw-r--r--" ); +my %file_slash = ( + 'name' => "really/special", + 'content' => "slash in the name \n", + 'time' => "Jan 17 11:01", + 'dostime' => "01-17-10 11:01AM", + 'perm' => "rw-rw-rw-" +); + my %lists = ( '/fully_simulated/percent/' => { - 'files' => [ \%file_someothertext_txt, \%file_percent_txt], + 'files' => [ \%file_someothertext_txt, \%file_percent_txt, + \%file_slash], 'eol' => "\r\n", 'type' => "unix" }, From 664d00d5db942bc05c3502c1571abfe31b96b0f6 Mon Sep 17 00:00:00 2001 From: Filippo Tedeschi Date: Tue, 22 Sep 2026 11:59:37 +0200 Subject: [PATCH 245/481] ws: do not auto-pong in raw mode CURLOPT_WS_OPTIONS(3) documents that in raw mode (CURLWS_RAW_MODE), libcurl does not handle pings or any other frame for the application. Previously, auto_pong only checked !data->set.ws_no_auto_pong, ignoring data->set.ws_raw_mode. As a result, receiving a PING in raw mode caused libcurl to intercept the frame, discard it from the application stream, and queue an automatic PONG into ws->pending. When sending raw data via curl_ws_send(), ws_flush() would inject this unexpected PONG control frame into the application's raw stream, corrupting message framing. Disable auto_pong whenever ws_raw_mode is active. Closes #23040 --- lib/ws.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/ws.c b/lib/ws.c index 51084b3669c0..3d20c5b4e2f5 100644 --- a/lib/ws.c +++ b/lib/ws.c @@ -669,7 +669,7 @@ static CURLcode ws_cw_dec_next(const uint8_t *buf, size_t buflen, struct ws_cw_dec_ctx *ctx = user_data; struct Curl_easy *data = ctx->data; struct websocket *ws = ctx->ws; - bool auto_pong = !data->set.ws_no_auto_pong; + bool auto_pong = !data->set.ws_raw_mode && !data->set.ws_no_auto_pong; curl_off_t remain; CURLcode result; @@ -1534,7 +1534,7 @@ static CURLcode ws_client_collect(const uint8_t *buf, size_t buflen, { struct ws_collect *ctx = userp; struct Curl_easy *data = ctx->data; - bool auto_pong = !data->set.ws_no_auto_pong; + bool auto_pong = !data->set.ws_raw_mode && !data->set.ws_no_auto_pong; curl_off_t remain; CURLcode result = CURLE_OK; From 1845ae1b8c0e5ae9a11703b8fed3e2fa3a4ab247 Mon Sep 17 00:00:00 2001 From: Daniel Stenberg Date: Fri, 25 Sep 2026 00:29:55 +0200 Subject: [PATCH 246/481] RELEASE-NOTES: synced --- RELEASE-NOTES | 96 +++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 86 insertions(+), 10 deletions(-) diff --git a/RELEASE-NOTES b/RELEASE-NOTES index f567ba78d3a7..76f16ea49266 100644 --- a/RELEASE-NOTES +++ b/RELEASE-NOTES @@ -5,7 +5,7 @@ curl and libcurl 8.23.0 curl_easy_setopt() options: 312 Public functions in libcurl: 100 Authors: 1536 - Contributors: 3819 + Contributors: 3823 This release includes the following changes: @@ -21,6 +21,11 @@ This release includes the following bugfixes: o build: convert HAS_TIME_T_UNSIGNED to a compile-time check [65] o build: drop `HTTP_ONLY` option [23] o capsule: reject a capsule that can never be buffered [84] + o cf-h1-proxy: reset chunk state on new request [146] + o cf-h1-proxy: stricter status line parsing [145] + o cf-ngtcp2-proxy: check for stream close on rx win update [159] + o cf-ngtcp2-proxy: fix rx flow control [174] + o cf-socket: clamp keepalive seconds-to-milliseconds conversions [167] o clang-tidy: merge nested `if()`s [39] o config2setopts: always set the security options for SCP/SFTP [67] o conncache: avoid evicting connect-only connections [79] @@ -30,13 +35,16 @@ This release includes the following bugfixes: o cookie: ignore individual cookie errors when loading [6] o cshutdown: make it smaller [60] o curl_addrinfo: drop redundant parentheses [41] + o curl_easy_send/recv: check pointer arg [175] o curl_fopen: restore the uid and gid checks [70] o curl_formadd.md: mention the strlen() for names as well [29] o CURLINFO_EFFECTIVE_URL.md: mention a short lifetime [116] o CURLOPT*ISSUERCERT*: not verified cryptographically [28] + o CURLOPT_POSTFIELDSIZE.md: improve wording [144] o CURLOPT_SHARE.md: remove bad explanation for cookie sharing [7] o CURLOPT_SSL_CTX_FUNCTION.md: client cert caveats [87] o cw-out: avoid O(n^2) work while buffering paused output [74] + o cw-out: recheck callbacks for each call [134] o cw-pause: fix O(N^2) list traversal in cw_pause_flush [137] o digest: check peer equality for staleness [24] o digest: fix maximum length quoted value parsing [22] @@ -48,6 +56,7 @@ This release includes the following bugfixes: o dynhds: grow header array geometrically [73] o easy: duplicating a zero byte memory area needs no malloc [91] o easy: make ca_cache_timeout standalone [51] + o easy: validate buffer pointer in curl_easy_recv and curl_easy_send [152] o escape: polish encoding and decoding for speed [37] o escape: use hex-pair lookup in curl_easy_escape [77] o examples: clean up crawler link parsing resources [119] @@ -57,11 +66,13 @@ This release includes the following bugfixes: o ftp: tighten use_ssl check for conn reuse [19] o ftp: URL encode file names when wildcarding [61] o ftplistparser: no more overwriting set.write_func [100] + o ftplistparser: when wildcarding, skip files with slashes [125] o getinfo: make sure CURLINFO_EFFECTIVE_URL does not contain creds [107] o getinfo: make sure CURLINFO_REDIRECT_URL does not contain creds [94] o getparam: switch off CONNECT headers when using -J [4] o glob: reduce the number of allocations for URLs that don't need globbing. [109] o global_init: tidy up, simplify, fix issues [43] + o gtls: no early return on verify fail [149] o happy eyeballs: bound work from many duplicate/failing addresses [80] o hash: change key arguments to const [14] o hash: vary bucket placement across process runs [108] @@ -78,7 +89,10 @@ This release includes the following bugfixes: o http: only enable Negotiate/NTLM to allowed origins [99] o http: reject resumed uploads [16] o httpsig-key.md: correct the generating Ed25519 keys commands [97] + o httpsrr+ech: check target and port [133] + o if2ip: check interface name case sensitive [158] o imap: compare selected mailbox names case-sensitively [57] + o inet_pton: reject a colon that ends an IPv6 address [179] o KNOWN_BUGS: drop the empty error buffer entry, it no longer happens [66] o ldap: base64-encode a non-SAFE DN and reject a control-byte type name [55] o lib: connection shutdown, cleanup code [92] @@ -86,12 +100,17 @@ This release includes the following bugfixes: o lib: reject internal handles in the multi/easy public API [103] o libssh2: match hashed known_hosts entries by host [30] o libssh2: skip repeat checkp() for already-absent hashed key types [82] + o libssh: work with empty priv_key [143] o mbedtls: fix ticket handling [52] + o mbedtls: resend length check [140] + o mime: bound Curl_mime_duppart() recursion depth [164] o mime: bound Curl_mime_prepare_headers() recursion depth [76] o mime: do curl_mime_free() non-recursively [48] o mprintf: avoid scanning strings twice for unbounded %s [110] o mprintf: stage output and emit it in runs [38] + o mprintf: use size_t width for the bare Windows %I modifier [178] o mqtt: drain queued output before advancing the state machine [118] + o mqtt: remaininglength check for 32-bit systems [150] o mulit_ev: remove socket references on forget [89] o multi-event: fix timeout of pending transfers [111] o multi: reduce struct size with small cleanups [25] @@ -103,26 +122,37 @@ This release includes the following bugfixes: o netrc: empty quoted values now produce a valid empty string [62] o netrc: survive comment-only .netrc file [93] o ngtcp2: shrink max sendbuf [13] + o openldap: hand it a dup'd socket, not curl's own o openssl/gtls: reject CRLfile with native CA store, matching rustls [27] o openssl: use non-deprecated API for MD5/MD4 [69] o os400: document curl_formadd_CCSID multi-chunk peculiarity [5] + o os400: restore HAVE_FCHMOD [181] o ossl_verifyhost: remove assumption of null termination of ASN1_STRING. [26] + o output-dir.md: expand on its behavior [151] o parsedate: find day names, months and time zones faster [68] o parsedate: remove the unused leading three letters [106] + o peer: parsing a zoneid as number fix [139] + o peer: preserve ipv6 props from "connect-to" [155] o perf-tests: output two-digital decimals always [96] o pingpong: add a max loop counter for Curl_pp_readresp [113] + o pytest: check server status without ssl verify [154] o rtsp: accept a Content-Length body on methods that expected none [115] o rtsp: do not send a learned session id to another origin [10] o runtests: Added runnernums in order to have O(1) lookup [45] o schannel: auto_client_cert conn matching [17] o schannel: clear borrowed sslContext on close [15] o socks: support CURLAUTH_ONLY in CURLOPT_SOCKS5_AUTH [124] + o socks: track SOCKS5 authentication per socket, not per connection [171] + o socksd: count connections per test, widen the method2 sentinel o ssl: dedup ssl_fsslctx [35] + o ssls: validate input arguments in import, export, and unpack [136] o strparse: speed up number parsing [129] + o sws: fix connection-monitor for connections closed before a request [153] o telnet: make it work through proxy tunnels [112] o test 1605: move from unit to libtest [49] o test1399: improve reliability [9] o test1679: unset `SSL_CERT_FILE`, use `TESTNUMBER` [58] + o test: restore the old test 1609 as 3271 [162] o tftp: verify direction in a few places [101] o tool: (re)set SSL verification in --libcurl output [1] o tool: buffer headers before remote filename selection [98] @@ -132,17 +162,25 @@ This release includes the following bugfixes: o tool_help: guard category[2] access when category is bare "-" [59] o tool_ipfs: keep the path percent-encoded when rewriting to the gateway [138] o tool_operate: clear the retry flag when the output file fails [122] + o tool_operate: set stdin non-blocking unconditionally [130] o u8_strset, u32_ptrset: fix boundaries handling [131] o uint-bset: skip unused slots when counting + o uint-table: add remove-max precaution [165] o url: match connections without force reuse and candidates [95] o url: ntlm/negotiate, prohibit connection reuse on follows [18] + o urlapi: make curl_url_dup() copy the guessed_scheme flag [166] + o urlapi: normalize legacy numeric IPv4 hosts set via CURLUPART_HOST [180] + o urlapi: return NULL from curl_url_dup() when given a NULL handle [160] o urlapi: run the urlparser perf test faster [54] o urldata: remove conn->given [40] o urldata: remove ssl_primary_config from easy handle [78] + o vdns: announce resolver start before the HTTPS RR query is sent [173] o vquic: fix preprocessor check for old Darwin versions [8] o vssh: do not busy-loop in blocking state machine without a timeout [121] o vtls: fix Curl_cert_hostcheck when the pattern is not a C string [12] o warnless: dedupe Intel compiler warning suppression pragmas [42] + o wolfssl: remove BIO destroy callback [157] + o ws: do not auto-pong in raw mode [123] o x509: stricter parsing [2] This release includes the following known bugs: @@ -164,18 +202,20 @@ Planned upcoming removals include: This release would not have looked like this without help, code, reports and advice from friends like these: - 1rhino2 on hackerone, Adam Shirt, Artem Prilutskiy, Aviv Engelberg, - Axel Mierczuk, Bobbeh Rhino, Christian Hesse, Christian Ullrich, Claude, + 1rhino2 on hackerone, Adam Shirt, Andreas Westin, Artem Prilutskiy, + Aviv Engelberg, Axel Mierczuk, Bobbeh Rhino, bupt-Yy-young on github, + Charles Muehlberger, Christian Hesse, Christian Ullrich, Claude, Dan Fandrich, Daniel Lang, Daniel Stenberg, David Benjamin, - Fengxiaoxx on github, Filippo Tedeschi, Florent Castelli, Fredrik Blau, - Huang Yiheng, Ilias Aberkane, Jorge Rocamora, Joshua Rogers, ldm0, Luna Tong, - Martin Dukek, Max Dymond, Mihai-Gabriel Marin, minnnjuuu, + Fengxiaoxx on github, Fernando Gallardo, Filippo Tedeschi, Florent Castelli, + Fredrik Blau, Huang Yiheng, Ilias Aberkane, Jorge Rocamora, Joshua Rogers, + ldm0, Luna Tong, Martin Dukek, Max Dymond, Mihai-Gabriel Marin, minnnjuuu, Muhamad Arga Reksapati, OSS-Fuzz, Paolo Ganci, Patrick Monnerat, rawsun007, Ray Satiro, renovate[bot], Rhino, Richard Payne, Ron Kuper, Roshan Ramani, - Rudolf Polzer, Samuel Henrique, Skye Soss, Stanislav Fort, Stefan Eissing, - Stephen Psaradellis, szedenik-adam on github, Thanniru Sai Teja, - Tyler Yankee, Un1q32, Viktor Szakats, xhon-pelushi, Yechan Bae - (51 contributors) + Rudolf Polzer, Samuel Henrique, Sebastian Raase, Skye Soss, Stanislav Fort, + Stefan Eissing, Stephen Psaradellis, szedenik-adam on github, + Thanniru Sai Teja, Tyler Yankee, Un1q32, Viktor Szakats, xhon-pelushi, + Yechan Bae + (56 contributors) References to bug reports and discussions on issues: @@ -301,12 +341,48 @@ References to bug reports and discussions on issues: [120] = https://curl.se/bug/?i=23002 [121] = https://curl.se/bug/?i=23005 [122] = https://curl.se/bug/?i=23001 + [123] = https://curl.se/bug/?i=23040 [124] = https://curl.se/bug/?i=23003 + [125] = https://curl.se/bug/?i=23089 [126] = https://curl.se/bug/?i=22995 [127] = https://curl.se/bug/?i=22997 [128] = https://curl.se/bug/?i=22996 [129] = https://curl.se/bug/?i=22993 + [130] = https://curl.se/bug/?i=23081 [131] = https://curl.se/bug/?i=22991 [132] = https://curl.se/bug/?i=22950 + [133] = https://curl.se/bug/?i=23086 + [134] = https://curl.se/bug/?i=23085 + [136] = https://curl.se/bug/?i=23062 [137] = https://curl.se/bug/?i=22968 [138] = https://curl.se/bug/?i=22944 + [139] = https://curl.se/bug/?i=23060 + [140] = https://curl.se/bug/?i=23083 + [143] = https://curl.se/bug/?i=23080 + [144] = https://curl.se/bug/?i=23078 + [145] = https://curl.se/bug/?i=23068 + [146] = https://curl.se/bug/?i=23069 + [149] = https://curl.se/bug/?i=23061 + [150] = https://curl.se/bug/?i=23071 + [151] = https://curl.se/bug/?i=23067 + [152] = https://curl.se/bug/?i=23063 + [153] = https://curl.se/bug/?i=23072 + [154] = https://curl.se/bug/?i=23064 + [155] = https://curl.se/bug/?i=23055 + [157] = https://curl.se/bug/?i=23058 + [158] = https://curl.se/bug/?i=23056 + [159] = https://curl.se/bug/?i=23054 + [160] = https://curl.se/bug/?i=23051 + [162] = https://curl.se/bug/?i=23052 + [164] = https://curl.se/bug/?i=23023 + [165] = https://curl.se/bug/?i=23034 + [166] = https://curl.se/bug/?i=23043 + [167] = https://curl.se/bug/?i=23045 + [171] = https://curl.se/bug/?i=23014 + [173] = https://curl.se/bug/?i=23037 + [174] = https://curl.se/bug/?i=23038 + [175] = https://curl.se/bug/?i=23035 + [178] = https://curl.se/bug/?i=23030 + [179] = https://curl.se/bug/?i=23028 + [180] = https://curl.se/bug/?i=23027 + [181] = https://curl.se/bug/?i=23026 From dd3e3fc3806fdd52052ee09fd0c44252882d63d5 Mon Sep 17 00:00:00 2001 From: Stefan Eissing Date: Fri, 25 Sep 2026 09:08:50 +0200 Subject: [PATCH 247/481] cpool: do not use admin handle for conn lookup The recent cpool refactor used an admin handle for looking up the last used connection of a transfer, but only for pool locks. In this use case, an admin handle it not always available (curl_easy_getinfo) and not necessary for pool locks. Just used the passed transfer for locking. Closes #23090 --- lib/conncache.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/conncache.c b/lib/conncache.c index 6ffc5f5bcfdf..6e695c4975f8 100644 --- a/lib/conncache.c +++ b/lib/conncache.c @@ -933,12 +933,11 @@ struct connectdata *Curl_cpool_get_last_conn(struct Curl_easy *data) { struct cpool *cpool = cpool_get_instance(data); if(cpool && (data->state.last_cpid != UINT32_MAX)) { - struct Curl_easy *admin = Curl_get_admin(data); struct connectdata *conn; - CPOOL_LOCK(cpool, admin); + CPOOL_LOCK(cpool, data); conn = Curl_uint32_tbl_get(&cpool->conns, data->state.last_cpid); - CPOOL_UNLOCK(cpool, admin); + CPOOL_UNLOCK(cpool, data); if(conn && (data->state.last_conn_id == conn->connection_id)) return conn; data->state.last_cpid = UINT32_MAX; From 44835a3f979f13c308dabc72e00910e4e1c0a32f Mon Sep 17 00:00:00 2001 From: Stefan Eissing Date: Fri, 25 Sep 2026 10:01:21 +0200 Subject: [PATCH 248/481] rustls: break recv loop on empty plain text Break out of rustls' receive loop on empty plaintext results (due to a TLS message not having any) to prevent the famous "malicious" server from keeping curl in that loop forever. Reported-by: Joshua Rogers Closes #23092 --- lib/vtls/rustls.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/vtls/rustls.c b/lib/vtls/rustls.c index 1a5cdd58f72a..ac2c3a9127b1 100644 --- a/lib/vtls/rustls.c +++ b/lib/vtls/rustls.c @@ -226,7 +226,10 @@ static CURLcode cr_recv(struct Curl_cfilter *cf, struct Curl_easy *data, plainlen - *pnread, &n); if(rresult == RUSTLS_RESULT_PLAINTEXT_EMPTY) { + /* A TLS message that carried no plaintext. Break out of the loop + * to prevent a server from keeping us here forever. */ backend->data_in_pending = FALSE; + break; } else if(rresult == RUSTLS_RESULT_UNEXPECTED_EOF) { failf(data, "rustls: peer closed TCP connection " From c28aeff2c337fc9b07419f137f0928d2f6bc4491 Mon Sep 17 00:00:00 2001 From: Stefan Eissing Date: Fri, 25 Sep 2026 09:49:15 +0200 Subject: [PATCH 249/481] vtls_scache: remember lock state locally Remember the locking state locally on import so that there is no confusion with another thread having locked the cache in the meantime. Reported-by: Joshua Rogers Closes #23091 --- lib/vtls/vtls_scache.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/vtls/vtls_scache.c b/lib/vtls/vtls_scache.c index 3867ba16f570..7192be2d369c 100644 --- a/lib/vtls/vtls_scache.c +++ b/lib/vtls/vtls_scache.c @@ -1216,6 +1216,7 @@ CURLcode Curl_ssl_session_import(struct Curl_easy *data, struct Curl_ssl_scache *scache = cf_ssl_scache_get(data); struct Curl_ssl_scache_peer *peer = NULL; struct Curl_ssl_session *s = NULL; + bool have_lock = FALSE; CURLcode result; if(!scache || !sdata || !sdata_len) { @@ -1232,6 +1233,7 @@ CURLcode Curl_ssl_session_import(struct Curl_easy *data, goto out; Curl_ssl_scache_lock(data); + have_lock = TRUE; if(ssl_peer_key) { result = cf_ssl_add_peer(data, scache, ssl_peer_key, NULL, &peer); @@ -1272,7 +1274,7 @@ CURLcode Curl_ssl_session_import(struct Curl_easy *data, } out: - if(scache && scache->is_locked) + if(scache && have_lock) Curl_ssl_scache_unlock(data); Curl_ssl_session_destroy(s); return result; From a2a0e4e1731ed6db3845126bcc021d33d005e177 Mon Sep 17 00:00:00 2001 From: Viktor Szakats Date: Fri, 25 Sep 2026 11:54:06 +0200 Subject: [PATCH 250/481] cf-ngtcp2-cmn: fix possible `-Wdeclaration-after-statement` E.g. (seen with LibreSSL build combined with ngtcp2 1.15.0): ``` lib/vquic/cf-ngtcp2-cmn.c:287:9: warning: mixing declarations and code is incompatible with standards before C99 [-Wdeclaration-after-statement] 287 | int ossl_early_status = SSL_get_early_data_status(ctx->tls.ossl.ssl); | ^ ``` Closes #23094 --- lib/vquic/cf-ngtcp2-cmn.c | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/lib/vquic/cf-ngtcp2-cmn.c b/lib/vquic/cf-ngtcp2-cmn.c index 0110a03f7013..9f59011b53cc 100644 --- a/lib/vquic/cf-ngtcp2-cmn.c +++ b/lib/vquic/cf-ngtcp2-cmn.c @@ -283,24 +283,26 @@ static int cb_ngtcp2_handshake_completed(ngtcp2_conn *tconn, void *user_data) ctx->earlydata_accepted = !ngtcp2_conn_get_tls_early_data_rejected2(ctx->qconn); #else /* older NGTCP2 */ + { #if defined(USE_OPENSSL) && defined(HAVE_OPENSSL_EARLYDATA) - int ossl_early_status = SSL_get_early_data_status(ctx->tls.ossl.ssl); - if(ossl_early_status == SSL_EARLY_DATA_NOT_SENT) - CURL_TRC_CF(data, cf, "OpenSSL did not send early data"); - ctx->earlydata_accepted = (ossl_early_status == SSL_EARLY_DATA_ACCEPTED); + int ossl_early_status = SSL_get_early_data_status(ctx->tls.ossl.ssl); + if(ossl_early_status == SSL_EARLY_DATA_NOT_SENT) + CURL_TRC_CF(data, cf, "OpenSSL did not send early data"); + ctx->earlydata_accepted = (ossl_early_status == SSL_EARLY_DATA_ACCEPTED); #elif defined(USE_GNUTLS) - int flags = gnutls_session_get_flags(ctx->tls.gtls.session); - ctx->earlydata_accepted = !!(flags & GNUTLS_SFLAGS_EARLY_DATA); + int flags = gnutls_session_get_flags(ctx->tls.gtls.session); + ctx->earlydata_accepted = !!(flags & GNUTLS_SFLAGS_EARLY_DATA); #elif defined(USE_WOLFSSL) #ifdef WOLFSSL_EARLY_DATA - ctx->earlydata_accepted = - (wolfSSL_get_early_data_status(ctx->tls.wssl.ssl) != - WOLFSSL_EARLY_DATA_REJECTED); + ctx->earlydata_accepted = + (wolfSSL_get_early_data_status(ctx->tls.wssl.ssl) != + WOLFSSL_EARLY_DATA_REJECTED); #else - DEBUGASSERT(0); /* should not come here if ED is disabled. */ - ctx->earlydata_accepted = FALSE; + DEBUGASSERT(0); /* should not come here if ED is disabled. */ + ctx->earlydata_accepted = FALSE; #endif /* WOLFSSL_EARLY_DATA */ #endif /* OPENSSL or GNUTLS or WOLFSSL */ + } #endif /* older NGTCP2 */ CURL_TRC_CF(data, cf, "server did%s accept %zu bytes of early data", ctx->earlydata_accepted ? "" : " not", ctx->earlydata_skip); From 57fffc3cc4cd6c830806bc3f6f60f96823ddacab Mon Sep 17 00:00:00 2001 From: Viktor Szakats Date: Fri, 25 Sep 2026 12:17:06 +0200 Subject: [PATCH 251/481] appveyor: drop two no-SSL jobs To improve CI performance. On the assumption that no-SSL builds have limited usefulness nowadays. VS2026 and VS2022 remains tested in other jobs. Saving 1m15s to 1m45s twice. Ref: https://github.com/curl/curl/pull/23094#issuecomment-5830709779 Closes #23095 --- appveyor.yml | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index ce63a060debf..25fceb67dba2 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -101,18 +101,6 @@ environment: CMAKE_GENERATOR: 'Visual Studio 17 2022' CMAKE_GENERATE: '-A x64 -DENABLE_UNICODE=ON -DENABLE_DEBUG=OFF' - - job_name: 'CM VS2026, Debug, x64, !ssl, Static, Build-tests' - APPVEYOR_BUILD_WORKER_IMAGE: 'Visual Studio 2026' - CMAKE_VERSION: 4.2.1 - CMAKE_SHA256: dfc2b2afac257555e3b9ce375b12b2883964283a366c17fec96cf4d17e4f1677 - CMAKE_GENERATOR: 'Visual Studio 18 2026' - CMAKE_GENERATE: '-A x64 -DBUILD_SHARED_LIBS=OFF -DCURL_USE_SCHANNEL=OFF' - - - job_name: 'CM VS2022, Debug, x64, !ssl, Static, Build-tests' - APPVEYOR_BUILD_WORKER_IMAGE: 'Visual Studio 2022' - CMAKE_GENERATOR: 'Visual Studio 17 2022' - CMAKE_GENERATE: '-A x64 -DBUILD_SHARED_LIBS=OFF -DCURL_USE_SCHANNEL=OFF' - # VisualStudioSolution builds - job_name: 'VisualStudioSolution VS2010, Release, x86, Schannel' From 89bcecd854debff95d0e37c19348e6cb9ef46780 Mon Sep 17 00:00:00 2001 From: Viktor Szakats Date: Thu, 24 Sep 2026 13:48:02 +0200 Subject: [PATCH 252/481] GHA/linux: move two steps to the background No measurable perf gain at the moment. Closes #23097 --- .github/workflows/linux.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 0665ecb673cd..974d498606eb 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -891,6 +891,7 @@ jobs: - name: 'single-use function check' if: ${{ (contains(matrix.build.configure, '--disable-unity') || contains(matrix.build.generate, '-DCMAKE_UNITY_BUILD=OFF')) && !contains(matrix.build.install_steps, 'filc') }} + background: true run: | git config --global --add safe.directory "*" if [ "${MATRIX_BUILD}" = 'cmake' ]; then @@ -928,8 +929,8 @@ jobs: - name: 'test-linter' if: ${{ contains(matrix.build.install_steps, 'test-linter') }} - run: - cd tests && ./test-linter.pl ../bld + background: true + run: cd tests && ./test-linter.pl ../bld - name: 'run tests' if: ${{ !contains(matrix.build.install_steps, 'skipall') && !contains(matrix.build.install_steps, 'skiprun') }} From ac29fbe7e22bb25f7d6ae762b70dbcdf8c3fe053 Mon Sep 17 00:00:00 2001 From: Viktor Szakats Date: Fri, 25 Sep 2026 14:11:38 +0200 Subject: [PATCH 253/481] GHA/windows: bump stunnel to 5.82 Seeing much more updates than in the past. Ref: https://www.stunnel.org/NEWS.html Closes #23098 --- .github/workflows/windows.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 88fb0d1f997d..2a82c9dff012 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -42,8 +42,8 @@ env: OPENSSH_WINDOWS_VERSION: 10.0.0.0p2-Preview OPENSSH_WINDOWS_SHA256_ARM64: 698c6aec31c1dd0fb996206e8741f4531a97355686b5431ef347d531b07fcd42 OPENSSH_WINDOWS_SHA256_WIN64: 23f50f3458c4c5d0b12217c6a5ddfde0137210a30fa870e98b29827f7b43aba5 - STUNNEL_VERSION: '5.80' - STUNNEL_SHA256: 25947bd268e2e670e1c7f915cefd54585aaa440d09eb20c6109cdbeaf3140bc4 + STUNNEL_VERSION: '5.82' + STUNNEL_SHA256: 722c1ad626919af2b0034666b4d19cc4795e925c32f60e8f3c59af4fb3ad2dfc jobs: build-cache: From 5c61b17893bf4d812ecc2b3093b57f9b6992c288 Mon Sep 17 00:00:00 2001 From: Stefan Eissing Date: Fri, 25 Sep 2026 14:23:04 +0200 Subject: [PATCH 254/481] apple-sectrust: check ocsp copy alloc Check the result of the OCSP data allocation and return CURLE_OUT_OF_MEMORY when this failed. Reported-by: Joshua Rogers Closes #23099 --- lib/vtls/apple.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/vtls/apple.c b/lib/vtls/apple.c index c6996f295ec8..45acbb3b7c2d 100644 --- a/lib/vtls/apple.c +++ b/lib/vtls/apple.c @@ -204,7 +204,10 @@ CURLcode Curl_vtls_apple_verify(struct Curl_cfilter *cf, #if defined(HAVE_BUILTIN_AVAILABLE) && defined(SUPPORTS_SecOCSP) if(__builtin_available(macOS 10.9, iOS 7, tvOS 9, watchOS 2, *)) { CFDataRef ocspdata = CFDataCreate(NULL, ocsp_buf, (CFIndex)ocsp_len); - + if(!ocspdata) { + result = CURLE_OUT_OF_MEMORY; + goto out; + } status = SecTrustSetOCSPResponse(trust, ocspdata); CFRelease(ocspdata); if(status != noErr) { From bdaff231dbc4eaff77d702895bf720300bc80dae Mon Sep 17 00:00:00 2001 From: Daniel Stenberg Date: Fri, 25 Sep 2026 14:20:28 +0200 Subject: [PATCH 255/481] imap: do size checks using 64-bit math To make them evaluate correctly also on 32-bit platforms. Reported-by: Mohammed K. Fathy Closes #23100 --- lib/imap.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/imap.c b/lib/imap.c index f06a20daf607..060715ed603e 100644 --- a/lib/imap.c +++ b/lib/imap.c @@ -1271,7 +1271,7 @@ static CURLcode imap_state_listsearch_resp(struct Curl_easy *data, pp->nfinal = 0; /* done */ /* Limit chunk to the literal size */ - if(chunk > (size_t)size) + if((curl_off_t)chunk > size) chunk = (size_t)size; if(chunk) { @@ -1439,7 +1439,7 @@ static CURLcode imap_state_fetch_resp(struct Curl_easy *data, curlx_dyn_tail(&pp->recvbuf, chunk); pp->nfinal = 0; /* done */ - if(chunk > (size_t)size) + if((curl_off_t)chunk > size) /* The conversion from curl_off_t to size_t is always fine here */ chunk = (size_t)size; From 4a17fbf0748a682807002a46aac0454ab4f06696 Mon Sep 17 00:00:00 2001 From: xhon-pelushi Date: Wed, 16 Sep 2026 19:41:53 -0400 Subject: [PATCH 256/481] KNOWN_BUGS: drop the curl-config private details entry It was added in 2005 (c7a634641f) with no linked issue, which is probably why nobody re-checked it. Two later changes fixed it without removing it: - 97aca0971d (2020) made --libs echo LIBCURL_PC_LIBS_PRIVATE only when ENABLE_SHARED=no, so a normal shared build prints just '-L$libdir -lcurl'. - e244d50064 (2024) filtered inherited LDFLAGS down to -L/-F/ --library-path=/-framework before they reach LIBCURL_PC_LDFLAGS_PRIVATE, and that variable is referenced only by --static-libs. Closes #23102 --- docs/KNOWN_BUGS.md | 6 ------ 1 file changed, 6 deletions(-) diff --git a/docs/KNOWN_BUGS.md b/docs/KNOWN_BUGS.md index 66f0b220526d..eeec20c76b34 100644 --- a/docs/KNOWN_BUGS.md +++ b/docs/KNOWN_BUGS.md @@ -115,12 +115,6 @@ this library to download. See [curl issue 5176](https://github.com/curl/curl/issues/5176) -## `curl-config --libs` contains private details - -`curl-config --libs` include details set in `LDFLAGS` when configure is run -that might be needed only for building libcurl. Further, `curl-config ---cflags` suffers from the same effects with `CFLAGS`/`CPPFLAGS`. - ## `LDFLAGS` passed too late making libs linked incorrectly Compiling latest curl on HP-UX and linking against a custom OpenSSL (which is From 790a21928403f997939e2285b3099b37b05b82a5 Mon Sep 17 00:00:00 2001 From: Stefan Eissing Date: Fri, 25 Sep 2026 15:19:36 +0200 Subject: [PATCH 257/481] http2: fix reset after 1xx response Do not think that response headers were complete when the stream was reset after an 1xx response. Reported-by: Joshua Rogers Closes #23104 --- lib/http2.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/http2.c b/lib/http2.c index 740e58317889..4c8b312fe7d8 100644 --- a/lib/http2.c +++ b/lib/http2.c @@ -987,17 +987,16 @@ static CURLcode on_stream_frame(struct Curl_cfilter *cf, return CURLE_RECV_ERROR; /* Only final status code signals the end of header */ - if(stream->status_code / 100 != 1) + if(stream->status_code / 100 != 1) { + stream->resp_hds_complete = TRUE; stream->bodystarted = TRUE; + } else stream->status_code = -1; h2_xfer_write_resp_hd(cf, data, stream, STRCONST("\r\n"), (bool)stream->closed); - if(stream->status_code / 100 != 1) { - stream->resp_hds_complete = TRUE; - } Curl_multi_mark_dirty(data); break; case NGHTTP2_PUSH_PROMISE: From 404eae3a2786a90a434f72ec427011f28cfab004 Mon Sep 17 00:00:00 2001 From: Viktor Szakats Date: Fri, 25 Sep 2026 20:31:51 +0200 Subject: [PATCH 258/481] GHA/http3-linux: do not set `BORINGSSL_VERSION` for quiche builds Quiche ships with its own BoringSSL copy which is/may be a different version than the one we pin in CI for the bare BoringSSL jobs. Cherry-picked from #23088 Closes #23106 --- .github/workflows/http3-linux.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/http3-linux.yml b/.github/workflows/http3-linux.yml index 886064ef2d22..6240260adbbc 100644 --- a/.github/workflows/http3-linux.yml +++ b/.github/workflows/http3-linux.yml @@ -774,13 +774,14 @@ jobs: - name: 'configure' env: LDFLAGS: '${{ matrix.build.LDFLAGS }}' + MATRIX_NAME: '${{ matrix.build.name }}' MATRIX_CONFIGURE: '${{ matrix.build.configure }}' MATRIX_GENERATE: '${{ matrix.build.generate }}' MATRIX_PKG_CONFIG_PATH: '${{ matrix.build.PKG_CONFIG_PATH }}' run: | [ -n "${MATRIX_PKG_CONFIG_PATH}" ] && export PKG_CONFIG_PATH="${MATRIX_PKG_CONFIG_PATH}" if [ "${MATRIX_BUILD}" = 'cmake' ]; then - [[ "${MATRIX_GENERATE}" = *'boringssl'* ]] && options=" -DBORINGSSL_VERSION=${BORINGSSL_VERSION}" + [[ "${MATRIX_NAME}" = *'boringssl'* ]] && options=" -DBORINGSSL_VERSION=${BORINGSSL_VERSION}" cmake -B bld -G Ninja \ -DCMAKE_C_COMPILER_TARGET="$(uname -m)-unknown-linux-gnu" -DBUILD_STATIC_LIBS=ON \ -DCURL_WERROR=ON -DENABLE_DEBUG=ON \ @@ -790,7 +791,7 @@ jobs: -DHTTPD_NGHTTPX=/home/runner/nghttp2/build/bin/nghttpx \ ${MATRIX_GENERATE} ${options} else - [[ "${MATRIX_CONFIGURE}" = *'boringssl'* ]] && export CPPFLAGS="-DCURL_BORINGSSL_VERSION=\\\"${BORINGSSL_VERSION}\\\"" + [[ "${MATRIX_NAME}" = *'boringssl'* ]] && export CPPFLAGS="-DCURL_BORINGSSL_VERSION=\\\"${BORINGSSL_VERSION}\\\"" mkdir bld && cd bld && ../configure --enable-warnings --enable-werror --enable-debug --disable-static \ --disable-dependency-tracking --enable-option-checking=fatal \ --with-libuv --enable-ntlm \ From 87f2436b6bbd039eec6e965bac1b7ebab2ba68de Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 25 Sep 2026 18:41:47 +0000 Subject: [PATCH 259/481] GHA: update dependency rustls/rustls-ffi to v0.15.4 With arm64 binaries. Thanks-to: Daniel McCarney Ref: https://github.com/rustls/rustls-ffi/pull/682 Ref: https://github.com/curl/curl/pull/23065#issuecomment-5813156375 Ref: https://github.com/curl/curl/pull/23079#issuecomment-5837164579 Closes #23107 --- .github/workflows/linux.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 974d498606eb..d3df7734a2a6 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -57,7 +57,7 @@ env: # renovate: datasource=github-releases depName=openssl/openssl versioning=semver extractVersion=^openssl-(?.+)$ registryUrl=https://github.com OPENSSL_VERSION: 4.0.2 # renovate: datasource=github-tags depName=rustls/rustls-ffi versioning=semver registryUrl=https://github.com - RUSTLS_VERSION: 0.15.3 + RUSTLS_VERSION: 0.15.4 # renovate: datasource=github-tags depName=wolfSSL/wolfssl versioning=semver extractVersion=^v?(?.+)-stable$ registryUrl=https://github.com WOLFSSL_VERSION: 5.9.2 From 9fab110871bc7a3f12a2dd77e60ce7b049324e34 Mon Sep 17 00:00:00 2001 From: Viktor Szakats Date: Fri, 25 Sep 2026 20:46:48 +0200 Subject: [PATCH 260/481] GHA/windows: fix MSVC arm job after upstream runner bump MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixing: ``` /usr/bin/find: ‘/c/PROGRA~1/MICROS~2/2022/Enterprise/vc/tools/msvc’: No such file or directory ``` Also: list installed MSVC versions, Windows Kits in log for information and debugging purposes. Ref: https://github.com/actions/runner-images/issues/14602 Closes #23108 --- .github/workflows/windows.yml | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 2a82c9dff012..599998a3b2ed 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -1068,16 +1068,21 @@ jobs: # The option to disable the MSBuild heuristics is likely called: # IgnoreStandardErrorWarningFormat=true, but there seems to be no # facility to pass it via CMake or CLI. - MSVC_EDITION='2022/Enterprise/vc/tools/msvc' - [[ "${MATRIX_IMAGE}" = *'windows-2025'* ]] && MSVC_EDITION='18/Enterprise/vc/tools/msvc' [[ "$(uname -s)" = *'ARM64'* ]] && MSVC_HOST='arm64' || MSVC_HOST='x64' # x86 MSVC_ROOTD="$(cygpath --mixed --short-name "$PROGRAMFILES/Microsoft Visual Studio")" # to avoid spaces in directory names + echo 'Installed MSVC versions:' + /usr/bin/find "$(cygpath --unix "$MSVC_ROOTD")" -mindepth 2 -maxdepth 2 -type d -name '*' || true + MSVC_EDITION='18/Enterprise/vc/tools/msvc' + echo 'Installed MSVC roots:' + /usr/bin/find "$(cygpath --unix "$MSVC_ROOTD/$MSVC_EDITION")" -mindepth 1 -maxdepth 1 -type d -name '*.*' MSVC_ROOTU="$(/usr/bin/find "$(cygpath --unix "$MSVC_ROOTD/$MSVC_EDITION")" -mindepth 1 -maxdepth 1 -type d -name '*.*' | sort | tail -n 1)" MSVC_ROOTW="$(cygpath --mixed "$MSVC_ROOTU")" MSVC_ROOTU="$(cygpath --unix "$MSVC_ROOTW")" MSVC_BINU="$MSVC_ROOTU/bin/Host$MSVC_HOST/$MATRIX_ARCH" MSDK_ROOTW="$(cygpath --mixed --short-name "$(printenv 'ProgramFiles(x86)')/Windows Kits")/10" MSDK_ROOTU="$(cygpath --unix "$MSDK_ROOTW")" + echo 'Installed Windows Kits:' + /usr/bin/find "$MSDK_ROOTU/lib" -mindepth 1 -maxdepth 1 -type d -name '*.*' MSDK_VER="$(basename "$(/usr/bin/find "$MSDK_ROOTU/lib" -mindepth 1 -maxdepth 1 -type d -name '*.*' | sort | tail -n 1)")" MSDK_LIBW="$MSDK_ROOTW/lib/$MSDK_VER" MSDK_INCW="$MSDK_ROOTW/include/$MSDK_VER" From e2625409e25fb8e71b40a298259202f3c9a61fb7 Mon Sep 17 00:00:00 2001 From: Viktor Szakats Date: Fri, 25 Sep 2026 22:07:46 +0200 Subject: [PATCH 261/481] GHA/linux: use Rustls bare zip package (was: .deb) To be on par with Rustls arm64 binary package. Also add `intel` suffix/comment to prepare for introducing arm64 binaries. Closes #23109 --- .github/workflows/linux.yml | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index d3df7734a2a6..06e6e6e2a9e6 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -176,19 +176,23 @@ jobs: - name: 'rustls valgrind 1' install_packages: libnghttp2-dev libldap-dev valgrind - install_steps: rust rustls + install_steps: rust rustls-intel + PKG_CONFIG_PATH: /home/runner/rustls/lib/pkgconfig tflags: '--subset=0/2' generate: -DCURL_USE_RUSTLS=ON -DUSE_ECH=ON -DENABLE_DEBUG=ON - name: 'rustls valgrind 2' install_packages: libnghttp2-dev libldap-dev valgrind - install_steps: rust rustls + install_steps: rust rustls-intel + PKG_CONFIG_PATH: /home/runner/rustls/lib/pkgconfig tflags: '--subset=1/2' generate: -DCURL_USE_RUSTLS=ON -DUSE_ECH=ON -DENABLE_DEBUG=ON - name: 'rustls' install_packages: libnghttp2-dev libldap-dev - install_steps: rust rustls skiprun pytest + install_steps: rust rustls-intel skiprun pytest + LDFLAGS: -Wl,-rpath,/home/runner/rustls/lib + PKG_CONFIG_PATH: /home/runner/rustls/lib/pkgconfig configure: --with-rustls --enable-ech --enable-debug - name: 'wolfssl-all' @@ -324,7 +328,7 @@ jobs: - name: 'clang-tidy' image: ubuntu-26.04 install_packages: clang-22 clang-tidy-22 libssl-dev libidn2-dev libssh2-1-dev libnghttp2-dev libldap-dev libkrb5-dev libgnutls28-dev - install_steps: skiprun mbedtls-latest-intel rustls wolfssl-opensslextra-intel + install_steps: skiprun mbedtls-latest-intel rustls-intel wolfssl-opensslextra-intel install_steps_brew: openssl@4 gsasl CC: clang-22 CFLAGS: -Wunused-macros @@ -795,28 +799,24 @@ jobs: cmake --build . cmake --install . - - name: 'cache rustls' - if: ${{ contains(matrix.build.install_steps, 'rustls') }} + - name: 'cache rustls (intel)' + if: ${{ contains(matrix.build.install_steps, 'rustls-intel') }} uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 - id: cache-rustls + id: cache-rustls-intel env: - cache-name: cache-rustls + cache-name: cache-rustls-intel with: path: ~/rustls key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ env.RUSTLS_VERSION }} - - name: 'fetch rustls deb' - if: ${{ contains(matrix.build.install_steps, 'rustls') && !steps.cache-rustls.outputs.cache-hit }} + - name: 'install rustls (intel)' + if: ${{ contains(matrix.build.install_steps, 'rustls-intel') && !steps.cache-rustls-intel.outputs.cache-hit }} run: | cd ~ curl --disable --fail --silent --show-error --connect-timeout 15 --max-time 120 --retry 6 --retry-connrefused \ - --location --proto-redir =https "https://github.com/rustls/rustls-ffi/releases/download/v${RUSTLS_VERSION}/librustls_${RUSTLS_VERSION}_amd64.deb.zip" --output pkg.bin + --location --proto-redir =https "https://github.com/rustls/rustls-ffi/releases/download/v${RUSTLS_VERSION}/rustls-ffi-x86_64-linux-gnu.zip" --output pkg.bin sha256sum pkg.bin && unzip pkg.bin -d rustls && rm -f pkg.bin - - - name: 'build rustls' - # Note: we do not check cache-hit here. If the cache is hit, we need to dpkg install the deb. - if: ${{ contains(matrix.build.install_steps, 'rustls') }} - run: sudo dpkg -i ~/rustls/"librustls_${RUSTLS_VERSION}_amd64.deb" + sed -i.bak 's|prefix=dist|prefix=/home/runner/rustls|g' rustls/lib/pkgconfig/rustls.pc; cat rustls/lib/pkgconfig/rustls.pc - name: 'install Intel compilers' if: ${{ contains(matrix.build.install_steps, 'intelc') }} From 49b34a08fa44e4610ecf0276314bbc1c7cbe654f Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 25 Sep 2026 21:16:18 +0000 Subject: [PATCH 262/481] GHA: update dependency wolfSSL/wolfssl to v5.9.4 Closes #23110 --- .github/workflows/http3-linux.yml | 2 +- .github/workflows/linux.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/http3-linux.yml b/.github/workflows/http3-linux.yml index 6240260adbbc..71d4022ea671 100644 --- a/.github/workflows/http3-linux.yml +++ b/.github/workflows/http3-linux.yml @@ -57,7 +57,7 @@ env: # renovate: datasource=github-tags depName=cloudflare/quiche versioning=semver registryUrl=https://github.com QUICHE_VERSION: 0.30.0 # renovate: datasource=github-tags depName=wolfSSL/wolfssl versioning=semver extractVersion=^v?(?.+)-stable$ registryUrl=https://github.com - WOLFSSL_VERSION: 5.9.2 + WOLFSSL_VERSION: 5.9.4 # renovate: datasource=github-tags depName=ngtcp2/nghttp3 versioning=semver registryUrl=https://github.com NGHTTP3_VERSION: 1.18.0 # renovate: datasource=github-tags depName=ngtcp2/ngtcp2 versioning=semver registryUrl=https://github.com diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 06e6e6e2a9e6..0db7a64a6e4c 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -59,7 +59,7 @@ env: # renovate: datasource=github-tags depName=rustls/rustls-ffi versioning=semver registryUrl=https://github.com RUSTLS_VERSION: 0.15.4 # renovate: datasource=github-tags depName=wolfSSL/wolfssl versioning=semver extractVersion=^v?(?.+)-stable$ registryUrl=https://github.com - WOLFSSL_VERSION: 5.9.2 + WOLFSSL_VERSION: 5.9.4 jobs: linux: From 5f921cb948961a12302a484bf47cac3d0818beba Mon Sep 17 00:00:00 2001 From: Viktor Szakats Date: Thu, 24 Sep 2026 13:53:00 +0200 Subject: [PATCH 263/481] GHA/linux: switch Rustls valgrind jobs to arm64 for >2x performance Reduces runtime for test steps in two jobs from 15-16 minutes to 6-7. Rustls-arm64-binaries-thanks-to: Daniel McCarney Ref: https://github.com/rustls/rustls-ffi/pull/682 Ref: https://github.com/curl/curl/pull/23065#issuecomment-5813156375 Follow-up to aff6f9332aa673a8cec31cde0be9992e84f0162d #23065 Closes #23079 --- .github/workflows/linux.yml | 29 +++++++++++++++++++++++++---- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 0db7a64a6e4c..d28906425d6f 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -66,7 +66,7 @@ jobs: name: ${{ matrix.build.generate && 'CM' || 'AM' }} ${{ matrix.build.name }} runs-on: ${{ matrix.build.image || 'ubuntu-latest' }} container: ${{ matrix.build.container }} - timeout-minutes: ${{ contains(matrix.build.install_packages, 'valgrind') && 30 || 15 }} + timeout-minutes: 15 env: MATRIX_BUILD: ${{ matrix.build.generate && 'cmake' || 'autotools' }} MATRIX_INSTALL_PACKAGES: '${{ matrix.build.install_packages }}' @@ -175,15 +175,17 @@ jobs: -DCURL_COMPLETION_FISH=ON -DCURL_COMPLETION_ZSH=ON - name: 'rustls valgrind 1' + image: ubuntu-24.04-arm install_packages: libnghttp2-dev libldap-dev valgrind - install_steps: rust rustls-intel + install_steps: rust rustls-arm PKG_CONFIG_PATH: /home/runner/rustls/lib/pkgconfig tflags: '--subset=0/2' generate: -DCURL_USE_RUSTLS=ON -DUSE_ECH=ON -DENABLE_DEBUG=ON - name: 'rustls valgrind 2' + image: ubuntu-24.04-arm install_packages: libnghttp2-dev libldap-dev valgrind - install_steps: rust rustls-intel + install_steps: rust rustls-arm PKG_CONFIG_PATH: /home/runner/rustls/lib/pkgconfig tflags: '--subset=1/2' generate: -DCURL_USE_RUSTLS=ON -DUSE_ECH=ON -DENABLE_DEBUG=ON @@ -818,6 +820,25 @@ jobs: sha256sum pkg.bin && unzip pkg.bin -d rustls && rm -f pkg.bin sed -i.bak 's|prefix=dist|prefix=/home/runner/rustls|g' rustls/lib/pkgconfig/rustls.pc; cat rustls/lib/pkgconfig/rustls.pc + - name: 'cache rustls (arm)' + if: ${{ contains(matrix.build.install_steps, 'rustls-arm') }} + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 + id: cache-rustls-arm + env: + cache-name: cache-rustls-arm + with: + path: ~/rustls + key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ env.RUSTLS_VERSION }} + + - name: 'install rustls (arm)' + if: ${{ contains(matrix.build.install_steps, 'rustls-arm') && !steps.cache-rustls-arm.outputs.cache-hit }} + run: | + cd ~ + curl --disable --fail --silent --show-error --connect-timeout 15 --max-time 120 --retry 6 --retry-connrefused \ + --location --proto-redir =https "https://github.com/rustls/rustls-ffi/releases/download/v${RUSTLS_VERSION}/rustls-ffi-arm64-linux-gnu.zip" --output pkg.bin + sha256sum pkg.bin && unzip pkg.bin -d rustls && rm -f pkg.bin + sed -i.bak 's|prefix=dist|prefix=/home/runner/rustls|g' rustls/lib/pkgconfig/rustls.pc; cat rustls/lib/pkgconfig/rustls.pc + - name: 'install Intel compilers' if: ${{ contains(matrix.build.install_steps, 'intelc') }} timeout-minutes: 4 @@ -934,7 +955,7 @@ jobs: - name: 'run tests' if: ${{ !contains(matrix.build.install_steps, 'skipall') && !contains(matrix.build.install_steps, 'skiprun') }} - timeout-minutes: ${{ contains(matrix.build.install_packages, 'valgrind') && 20 || 10 }} + timeout-minutes: ${{ contains(matrix.build.install_packages, 'valgrind') && 15 || 10 }} env: TEST_TARGET: ${{ matrix.build.torture && 'test-torture' || 'test-ci' }} TFLAGS: '${{ matrix.build.tflags }}' From d79df5711006edc6409c1f5ee83f13983b5174c2 Mon Sep 17 00:00:00 2001 From: Viktor Szakats Date: Fri, 25 Sep 2026 23:55:53 +0200 Subject: [PATCH 264/481] tests: require valgrind 3+ (2007-08-03) Also drop detection and compatibility logic for older versions. Ref: https://valgrind.org/downloads/old.html Closes #23111 --- docs/DEPENDENCIES.md | 1 + tests/runner.pm | 8 ++------ tests/runtests.pl | 22 ---------------------- 3 files changed, 3 insertions(+), 28 deletions(-) diff --git a/docs/DEPENDENCIES.md b/docs/DEPENDENCIES.md index efa659cb9350..50efca868eee 100644 --- a/docs/DEPENDENCIES.md +++ b/docs/DEPENDENCIES.md @@ -75,3 +75,4 @@ that do not find the necessary requirements are automatically skipped. - pytest - Python 3.8 (2019-10-14) - stunnel +- valgrind 3.0.0 (2007-08-03) diff --git a/tests/runner.pm b/tests/runner.pm index 627307104d97..b3241d68dcc9 100644 --- a/tests/runner.pm +++ b/tests/runner.pm @@ -64,8 +64,6 @@ BEGIN { $gdbxwin $shallow $tortalloc - $valgrind_logfile - $valgrind_tool ); # these are for debugging only @@ -115,8 +113,6 @@ use memanalyzer; # Global variables set elsewhere but used only by this package # These may only be set *before* runner_init is called our $DBGCURL = $CURL; #"../src/.libs/curl"; # alternative for debugging -our $valgrind_logfile = "--log-file"; # the option name for valgrind >=3 -our $valgrind_tool = "--tool=memcheck"; our $gdb = checktestcmd("gdb"); our $gdbthis = 0; # run test case with debugger (gdb or lldb) our $gdbxwin; # use windowed gdb when using gdb @@ -994,12 +990,12 @@ sub singletest_run { if(use_valgrind() && !$disablevalgrind) { my $valgrindcmd = "$valgrind "; - $valgrindcmd .= "$valgrind_tool " if($valgrind_tool); + $valgrindcmd .= "--tool=memcheck "; # valgrind 2.1.x+ $valgrindcmd .= "--quiet --leak-check=yes "; $valgrindcmd .= "--suppressions=$srcdir/valgrind.supp "; # $valgrindcmd .= "--gen-suppressions=all "; $valgrindcmd .= "--num-callers=16 "; - $valgrindcmd .= "${valgrind_logfile}=$LOGDIR/valgrind$testnum"; + $valgrindcmd .= "--log-file=$LOGDIR/valgrind$testnum"; # valgrind >=3 $CMDLINE = "$valgrindcmd $CMDLINE"; } diff --git a/tests/runtests.pl b/tests/runtests.pl index 98f469687ce3..12213ad12f97 100755 --- a/tests/runtests.pl +++ b/tests/runtests.pl @@ -2692,14 +2692,6 @@ sub pickrunner { undef $valgrind; } else { - # since valgrind 2.1.x, '--tool' option is mandatory - # use it, if it is supported by the version installed on the system - # (this happened in 2003, so we could probably do not need to care about - # that old version any longer and delete this check) - runclient("valgrind --help 2>&1 | grep -- --tool >$dev_null 2>&1"); - if(($? >> 8)) { - $valgrind_tool = ""; - } open(my $curlh, "<", $CURL); my $l = <$curlh>; if($l =~ /^\#\!/) { @@ -2707,20 +2699,6 @@ sub pickrunner { $valgrind = "../libtool --mode=execute $valgrind"; } close($curlh); - - # valgrind 3 renamed the --logfile option to --log-file!!! - # (this happened in 2005, so we could probably do not need to care about - # that old version any longer and delete this check) - my $ver = join(' ', runclientoutput("valgrind --version")); - # cut off all but digits and dots - $ver =~ s/[^0-9.]//g; - - if($ver =~ /^(\d+)/) { - $ver = $1; - if($ver < 3) { - $valgrind_logfile = "--logfile"; - } - } } } From 3a0d22a103cd841173be6f9997fbd7c5bc3bc148 Mon Sep 17 00:00:00 2001 From: Viktor Szakats Date: Sat, 26 Sep 2026 00:12:11 +0200 Subject: [PATCH 265/481] GHA/linux: skip test 1674 in valgrind jobs To save ~45 seconds in 5 jobs, ~25 in 1 job (~4+ minutes total). HSTS low-level functions remain valgrind-tests via 1660. Follow-up to 5f921cb948961a12302a484bf47cac3d0818beba #23079 Closes #23113 --- .github/workflows/linux.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index d28906425d6f..2ebb6ed021d6 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -963,6 +963,7 @@ jobs: if [ "${TEST_TARGET}" = 'test-ci' ] && [[ "${MATRIX_INSTALL_PACKAGES}" = *'valgrind'* ]]; then TFLAGS+=' -j6' TFLAGS+=' !776' # skip long-running flaky test + TFLAGS+=' !1674' # skip long-running test if [[ "${MATRIX_INSTALL_PACKAGES}" = *'libgss-dev'* ]]; then TFLAGS+=' ~2077 ~2078' # memory leaks from Curl_auth_decode_spnego_message() -> gss_init_sec_context() fi From c63d1c057f6e5a53b6740fbbda64275db62a5e19 Mon Sep 17 00:00:00 2001 From: Viktor Szakats Date: Sat, 26 Sep 2026 00:52:52 +0200 Subject: [PATCH 266/481] DEPENDENCIES.md: fix release date for valgrind 3.0.0 to 2005-08-03 Ref: https://valgrind.org/info/news.html Follow-up to d79df5711006edc6409c1f5ee83f13983b5174c2 #23111 --- docs/DEPENDENCIES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/DEPENDENCIES.md b/docs/DEPENDENCIES.md index 50efca868eee..1e9b5a94b3de 100644 --- a/docs/DEPENDENCIES.md +++ b/docs/DEPENDENCIES.md @@ -75,4 +75,4 @@ that do not find the necessary requirements are automatically skipped. - pytest - Python 3.8 (2019-10-14) - stunnel -- valgrind 3.0.0 (2007-08-03) +- valgrind 3.0.0 (2005-08-03) From 98519dac83b42317bdd22b296f493090d2e56fd6 Mon Sep 17 00:00:00 2001 From: Viktor Szakats Date: Sat, 26 Sep 2026 00:23:34 +0200 Subject: [PATCH 267/481] GHA/linux: pin ubuntu-24.04 in jobs where it makes sense To prepare for ubuntu-latest switching to ubuntu-26.04 soon. Keep ubuntu-latest where using locally built dependencies, pin to ubuntu-24.04 where using system packages. Also: - tested all ubuntu-latest jobs with ubuntu-26.04. Ref: https://github.com/actions/runner-images/issues/14748 Closes #23114 --- .github/workflows/linux.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 2ebb6ed021d6..2e488a41bd25 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -220,6 +220,7 @@ jobs: configure: --with-wolfssl=/home/runner/wolfssl-opensslextra --enable-ech --enable-debug --enable-httpsig - name: 'openssl default' + image: ubuntu-24.04 install_steps: pytest configure: --with-openssl --enable-debug --disable-unity @@ -238,6 +239,7 @@ jobs: generate: -DENABLE_DEBUG=ON -DENABLE_THREADED_RESOLVER=OFF -DCURL_ENABLE_NTLM=ON - name: 'openssl intel C89' + image: ubuntu-24.04 install_packages: libssh-dev install_steps: pytest configure: CFLAGS=-std=gnu89 --with-openssl --with-libssh --enable-debug @@ -249,18 +251,21 @@ jobs: configure: CFLAGS=-std=gnu89 --with-openssl --with-libssh2 --enable-debug --disable-verbose - name: 'openssl -O3 libssh valgrind 1' + image: ubuntu-24.04 install_packages: libssh-dev valgrind CFLAGS: -O3 tflags: '--subset=0/2' generate: -DENABLE_DEBUG=ON -DCURL_USE_LIBSSH=ON -DCMAKE_UNITY_BUILD_BATCH_SIZE=50 -DCURL_ENABLE_NTLM=ON - name: 'openssl -O3 libssh valgrind 2' + image: ubuntu-24.04 install_packages: libssh-dev valgrind CFLAGS: -O3 tflags: '--subset=1/2' generate: -DENABLE_DEBUG=ON -DCURL_USE_LIBSSH=ON -DCMAKE_UNITY_BUILD_BATCH_SIZE=50 -DCURL_ENABLE_NTLM=ON - name: 'openssl clang krb5 openldap static' + image: ubuntu-24.04 install_steps: openldap-static install_packages: libidn2-dev libkrb5-dev clang libssl-dev CC: clang @@ -313,6 +318,7 @@ jobs: generate: -DCURL_USE_OPENSSL=ON -DENABLE_DEBUG=ON -DENABLE_ARES=ON -DCURL_ENABLE_NTLM=ON -DCURL_DISABLE_HTTPSIG=OFF - name: 'openssl i686' + image: ubuntu-24.04 install_packages: gcc-14-i686-linux-gnu libssl-dev:i386 libssh2-1-dev:i386 libidn2-dev:i386 libc-ares-dev:i386 zlib1g-dev:i386 CC: i686-linux-gnu-gcc-14 LDFLAGS: -L/usr/lib/i386-linux-gnu @@ -438,6 +444,7 @@ jobs: tflags: '--min=1540' - name: 'event-based' + image: ubuntu-24.04 install_packages: libssh-dev configure: --enable-debug --enable-static --disable-shared --disable-threaded-resolver --with-libssh --with-openssl --enable-ntlm tflags: '-n --test-event --min=1420' From 941cda6f8d9aea37d7d41a8829243eafd07f5236 Mon Sep 17 00:00:00 2001 From: Daniel Stenberg Date: Fri, 25 Sep 2026 23:10:51 +0200 Subject: [PATCH 268/481] mprintf: make mprintf() and mfprintf() return -1 on error Previously they returned the number of characters it output at the time of the error, but that is not only contrary to what POSIX does, it also makes it next to impossible for a caller to know when for example the output failed because out of disk space or similar. Verified by test 2380 Closes #23112 --- docs/libcurl/curl_mprintf.md | 3 ++ lib/mprintf.c | 54 ++++++++++++++-------- tests/data/Makefile.am | 2 +- tests/data/test2380 | 21 +++++++++ tests/libtest/Makefile.inc | 1 + tests/libtest/lib2380.c | 88 ++++++++++++++++++++++++++++++++++++ 6 files changed, 150 insertions(+), 19 deletions(-) create mode 100644 tests/data/test2380 create mode 100644 tests/libtest/lib2380.c diff --git a/docs/libcurl/curl_mprintf.md b/docs/libcurl/curl_mprintf.md index a5a6c9a2c5b6..dc778b595824 100644 --- a/docs/libcurl/curl_mprintf.md +++ b/docs/libcurl/curl_mprintf.md @@ -288,3 +288,6 @@ a newly allocated string, or NULL if it failed. All other functions return the number of characters actually printed (excluding the null byte used to end output to strings). Note that this sometimes differ from how the POSIX versions of these functions work. + +Since libcurl 8.23.0 curl_mfprintf, curl_mprintf, curl_mvfprintf and +curl_mvprintf return -1 when the output fails. diff --git a/lib/mprintf.c b/lib/mprintf.c index 1203008bbb05..1ad26d39e2c5 100644 --- a/lib/mprintf.c +++ b/lib/mprintf.c @@ -139,6 +139,11 @@ struct asprintf { unsigned char stage[ASPRINTF_STAGE_SIZE]; }; +struct ioprintf { + FILE *fd; + bool error; +}; + /* the provided input number is 1-based but this returns the number 0-based. * * returns -1 if no valid number was provided. @@ -1449,37 +1454,55 @@ int curl_msprintf(char *buffer, const char *format, ...) static int fputc_wrapper(unsigned char outc, void *f) { + struct ioprintf *i = f; int out = outc; - FILE *s = f; + FILE *s = i->fd; int rc = fputc(out, s); - return rc == EOF; + i->error = (rc == EOF); + return (rc == EOF); } /* block variant of fputc_wrapper */ static size_t fwrite_wrapper(const unsigned char *buf, size_t len, void *f) { - FILE *s = f; - return fwrite(buf, 1, len, s); + struct ioprintf *i = f; + size_t n; + FILE *s = i->fd; + n = fwrite(buf, 1, len, s); + i->error = (n != len); + return n; } -int curl_mprintf(const char *format, ...) +int curl_mvfprintf(FILE *fd, const char *format, va_list args) +{ + struct ioprintf info; + int n; + info.fd = fd; + info.error = FALSE; + n = formatf(&info, fputc_wrapper, fwrite_wrapper, NULL, format, args); + if(info.error) + return -1; + return n; +} + +int curl_mfprintf(FILE *fd, const char *format, ...) { - int retcode; va_list args; /* argument pointer */ + int n; va_start(args, format); - retcode = formatf(stdout, fputc_wrapper, fwrite_wrapper, NULL, format, args); + n = curl_mvfprintf(fd, format, args); va_end(args); - return retcode; + return n; } -int curl_mfprintf(FILE *fd, const char *format, ...) +int curl_mprintf(const char *format, ...) { - int retcode; va_list args; /* argument pointer */ + int n; va_start(args, format); - retcode = formatf(fd, fputc_wrapper, fwrite_wrapper, NULL, format, args); + n = curl_mvfprintf(stdout, format, args); va_end(args); - return retcode; + return n; } int curl_mvsprintf(char *buffer, const char *format, va_list args) @@ -1491,10 +1514,5 @@ int curl_mvsprintf(char *buffer, const char *format, va_list args) int curl_mvprintf(const char *format, va_list args) { - return formatf(stdout, fputc_wrapper, fwrite_wrapper, NULL, format, args); -} - -int curl_mvfprintf(FILE *fd, const char *format, va_list args) -{ - return formatf(fd, fputc_wrapper, fwrite_wrapper, NULL, format, args); + return curl_mvfprintf(stdout, format, args); } diff --git a/tests/data/Makefile.am b/tests/data/Makefile.am index 47085a421b6b..5ab507ea5c77 100644 --- a/tests/data/Makefile.am +++ b/tests/data/Makefile.am @@ -1967,7 +1967,7 @@ TESTCASES = \ test2311 \ test2318 \ test2349 \ - \ + test2380 \ test2397 \ \ test2400 \ diff --git a/tests/data/test2380 b/tests/data/test2380 new file mode 100644 index 000000000000..d069695665ca --- /dev/null +++ b/tests/data/test2380 @@ -0,0 +1,21 @@ + + + + +libtest +curl_mprintf errors + + + + + +curl_mprintf errors on out of space + + +%LOGDIR/write2380 + + + + + + diff --git a/tests/libtest/Makefile.inc b/tests/libtest/Makefile.inc index d1b4204ce076..99e518567cfe 100644 --- a/tests/libtest/Makefile.inc +++ b/tests/libtest/Makefile.inc @@ -310,6 +310,7 @@ TESTS_C = \ lib2306.c \ lib2308.c \ lib2309.c \ + lib2380.c \ lib2397.c \ lib2402.c \ lib2404.c \ diff --git a/tests/libtest/lib2380.c b/tests/libtest/lib2380.c new file mode 100644 index 000000000000..e3d05809de51 --- /dev/null +++ b/tests/libtest/lib2380.c @@ -0,0 +1,88 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ +#include "first.h" +#ifdef HAVE_SYS_RESOURCE_H +#include /* RLIMIT_FSIZE */ +#endif +#include /* SIGXFSZ */ + +#if defined(HAVE_GETRLIMIT) && defined(HAVE_SETRLIMIT) && \ + defined(RLIMIT_FSIZE) && defined(SIGXFSZ) +/* set a tiny limit to trigger easily */ +#define T2380_FSIZE_LIMIT 50 +#endif + +static CURLcode test_lib2380(const char *URL) +{ + CURLcode result = CURLE_OK; +#ifdef T2380_FSIZE_LIMIT + struct rlimit rl_orig; + struct rlimit rl; + FILE *f; + + if(getrlimit(RLIMIT_FSIZE, &rl_orig)) { + curl_mfprintf(stderr, "getrlimit() failed\n"); + goto test_cleanup; + } + signal(SIGXFSZ, SIG_IGN); + rl = rl_orig; + rl.rlim_cur = T2380_FSIZE_LIMIT; + if(setrlimit(RLIMIT_FSIZE, &rl)) { + curl_mfprintf(stderr, "setrlimit() failed\n"); + goto test_cleanup; + } + + f = curlx_fopen(URL, "wb"); + if(f) { + int i; + int n; + + /* switch off stream buffering */ + setvbuf(f, NULL, _IONBF, 0); + + /* this should fail already in lap 1 */ + for(i = 0; i < 4; i++) { + n = curl_mfprintf(f, + "012345678901234567890123456789012345678901234567\n"); + if(n == -1) { + curl_mfprintf(stderr, "error on lap %d\n", i); + break; + } + } + if(i != 1) { + curl_mfprintf(stderr, "got loop %d instead of the expected 1\n", i); + result = TEST_ERR_FAILURE; + } + curlx_fclose(f); + } + + /* restore the original value */ + setrlimit(RLIMIT_FSIZE, &rl_orig); + +test_cleanup: +#else + (void)URL; /* unused */ +#endif + return result; +} From 47c08c8282aba97ffdaf3095b713620c83465c5f Mon Sep 17 00:00:00 2001 From: Viktor Szakats Date: Sat, 26 Sep 2026 14:08:37 +0200 Subject: [PATCH 269/481] GHA: update dependency cloudflare/quiche to v0.30.0 (revert) It takes longer than expected to figure out how to keep building it. (autotools works already. CMake not yet.) This reverts commit efc5e67bda67b9573694e820cc8c1273f01ce937 #22988 Ref: #23088 Closes #23116 --- .github/workflows/http3-linux.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/http3-linux.yml b/.github/workflows/http3-linux.yml index 71d4022ea671..75b2435ed206 100644 --- a/.github/workflows/http3-linux.yml +++ b/.github/workflows/http3-linux.yml @@ -55,7 +55,7 @@ env: OPENSSL_PREV_VERSION: 3.6.2 OPENSSL_PREV_SHA256: aaf51a1fe064384f811daeaeb4ec4dce7340ec8bd893027eee676af31e83a04f # renovate: datasource=github-tags depName=cloudflare/quiche versioning=semver registryUrl=https://github.com - QUICHE_VERSION: 0.30.0 + QUICHE_VERSION: 0.29.3 # renovate: datasource=github-tags depName=wolfSSL/wolfssl versioning=semver extractVersion=^v?(?.+)-stable$ registryUrl=https://github.com WOLFSSL_VERSION: 5.9.4 # renovate: datasource=github-tags depName=ngtcp2/nghttp3 versioning=semver registryUrl=https://github.com From 1fd01914286378e3db040b8ea8415fed317a5268 Mon Sep 17 00:00:00 2001 From: Viktor Szakats Date: Sat, 26 Sep 2026 14:22:57 +0200 Subject: [PATCH 270/481] GHA/http3-linux: reduce quiche cache blob size With quiche: - 0.29.3: 170 -> 87MB - 0.30.0: 220 -> 120MB Cherry-picked from #23088 Closes #23117 --- .github/workflows/http3-linux.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/http3-linux.yml b/.github/workflows/http3-linux.yml index 75b2435ed206..50efeb3094b2 100644 --- a/.github/workflows/http3-linux.yml +++ b/.github/workflows/http3-linux.yml @@ -757,6 +757,10 @@ jobs: mkdir -p quiche/boringssl/lib find quiche/target/release \( -name libcrypto.a -o -name libssl.a \) -exec ln -vnf -- '{}' quiche/boringssl/lib \; find quiche/target/release/build/boring-sys-*/out/boringssl/src -maxdepth 1 \( -name include \) -exec ln -vsf -- '../../{}' quiche/boringssl \; + # Reduce cached blob size + rm -rf quiche/target/release/build/boring-sys-*/out/build/CMakeFiles + rm -rf quiche/target/release/deps + du -t 4MB quiche # include dir # /home/runner/quiche/boringssl/include From a9396148364758269b8b8621d90683912da4c2fe Mon Sep 17 00:00:00 2001 From: Viktor Szakats Date: Sat, 26 Sep 2026 15:11:54 +0200 Subject: [PATCH 271/481] GHA/curl-for-win: drop redundant env No need to set llvm version for a gcc job. Closes #23118 --- .github/workflows/curl-for-win.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/curl-for-win.yml b/.github/workflows/curl-for-win.yml index 69238b45c81b..d46cd32f258e 100644 --- a/.github/workflows/curl-for-win.yml +++ b/.github/workflows/curl-for-win.yml @@ -59,7 +59,6 @@ jobs: export CW_CONFIG='-main-werror-unitybatch-nocertdata-linux-a64-x64-gcc' export CW_REVISION="${GITHUB_SHA}" . ./_versions.sh - export CW_CCSUFFIX='-15' export CW_GCCSUFFIX='-12' sudo podman image trust set --type reject default sudo podman image trust set --type accept docker.io/library From 0feb52b3ba0fbbed3747c400cdb4a1f7725c1252 Mon Sep 17 00:00:00 2001 From: Daniel Stenberg Date: Fri, 25 Sep 2026 15:08:51 +0200 Subject: [PATCH 272/481] cf-socket: adjust NO_SYN_RETRANSMISSIONS for older Windows The flag TCP_INITIAL_RTO_NO_SYN_RETRANSMISSIONS, could not be set to zero before Windows 10, version 1709. Instead set it to 1 on those system. Ref: https://mail.openjdk.org/pipermail/net-dev/2020-July/014213.html Reported-by: aschank on github Fixes #23084 Closes #23103 --- lib/cf-socket.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/cf-socket.c b/lib/cf-socket.c index 379c3bfb7ccb..08a0d74c5053 100644 --- a/lib/cf-socket.c +++ b/lib/cf-socket.c @@ -1189,7 +1189,13 @@ static void tcplocalhost(struct Curl_cfilter *cf, DWORD bytes = 0; memset(&rto, 0, sizeof(rto)); rto.Rtt = TCP_INITIAL_RTO_DEFAULT_RTT; - rto.MaxSynRetransmissions = TCP_INITIAL_RTO_NO_SYN_RETRANSMISSIONS; + /* Windows 10, version 1709 (10.0.16299) and later versions can set max + retransmissions to zero, earlier versions could only set 1 */ + if(curlx_verify_windows_version(10, 0, 16299, PLATFORM_WINNT, + VERSION_GREATER_THAN_EQUAL)) + rto.MaxSynRetransmissions = TCP_INITIAL_RTO_NO_SYN_RETRANSMISSIONS; + else + rto.MaxSynRetransmissions = 1; (void)WSAIoctl(sockfd, SIO_TCP_INITIAL_RTO, &rto, sizeof(rto), NULL, 0, &bytes, NULL, NULL); } From 755e36ead1e8dfb8d9c650359afd81f5d3d8eb05 Mon Sep 17 00:00:00 2001 From: Viktor Szakats Date: Sat, 26 Sep 2026 10:00:37 +0200 Subject: [PATCH 273/481] GHA/curl-for-win: switch to arm64 runners for Linux and Windows builds (5 jobs) Slightly faster than Intel. Also more efficient. Saving ~3 minutes. Refs: https://github.com/curl/curl-for-win/commit/5d368c620abfbd769d81076c45c69b3478925d66 https://github.com/curl/curl-for-win/commit/652a3e9b733cea19e9fbfb9690dab05ac44f5de6 Closes #23115 --- .github/workflows/curl-for-win.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/curl-for-win.yml b/.github/workflows/curl-for-win.yml index d46cd32f258e..b915a4e0ed9d 100644 --- a/.github/workflows/curl-for-win.yml +++ b/.github/workflows/curl-for-win.yml @@ -44,7 +44,7 @@ env: jobs: linux-glibc-gcc: name: 'Linux gcc glibc (amd64, arm64)' - runs-on: ubuntu-26.04 + runs-on: ubuntu-26.04-arm timeout-minutes: 10 steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 @@ -72,7 +72,7 @@ jobs: linux-glibc-gcc-minimal: # use gcc to minimize installed packages name: 'Linux gcc glibc minimal (amd64)' - runs-on: ubuntu-26.04 + runs-on: ubuntu-26.04-arm timeout-minutes: 5 steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 @@ -99,7 +99,7 @@ jobs: linux-musl-llvm: name: 'Linux llvm MUSL (amd64, riscv64)' - runs-on: ubuntu-26.04 + runs-on: ubuntu-26.04-arm timeout-minutes: 10 steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 @@ -148,7 +148,7 @@ jobs: win-llvm: name: 'Windows llvm (x64)' - runs-on: ubuntu-26.04 + runs-on: ubuntu-26.04-arm timeout-minutes: 10 env: CW_PKG_NODELETE: '1' @@ -185,7 +185,7 @@ jobs: win-gcc-zlibold-x64: name: 'Windows gcc zlib-classic (x64)' - runs-on: ubuntu-26.04 + runs-on: ubuntu-26.04-arm timeout-minutes: 10 steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 From 49eaaddd263c2ab181c40c9878f50ded517f99f3 Mon Sep 17 00:00:00 2001 From: Tip ten Brink <75669206+tiptenbrink@users.noreply.github.com> Date: Sat, 26 Sep 2026 19:37:08 +0200 Subject: [PATCH 274/481] http_aws_sigv4: fix the s3express service name - s3-express -> s3express in the disable double encoding list. - Also, add s3-object-lambda to the disable double encoding list. - Verify with test 2418 that double encode is not used for s3express by checking signature. Prior to this change s3express (due to typo) and s3-object-lambda were not excluded from the double encoding which would result in a bad signature and then the server would return SignatureDoesNotMatch. Fixes https://github.com/curl/curl/issues/23120 Closes https://github.com/curl/curl/pull/23121 --- lib/http_aws_sigv4.c | 11 +++++---- tests/data/Makefile.am | 1 + tests/data/test2418 | 53 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 60 insertions(+), 5 deletions(-) create mode 100644 tests/data/test2418 diff --git a/lib/http_aws_sigv4.c b/lib/http_aws_sigv4.c index d46a6a10cfa7..135d7a4d042c 100644 --- a/lib/http_aws_sigv4.c +++ b/lib/http_aws_sigv4.c @@ -264,13 +264,14 @@ static bool should_urlencode(struct Curl_str *service_name) { /* * These services require unmodified (not additionally URL-encoded) URL - * paths. - * should_urlencode == true is equivalent to should_urlencode_uri_path - * from the AWS SDK. Urls are already normalized by the curl URL parser + * paths. They are the S3 signing names, which have disableDoubleEncoding + * set in the AWS S3 endpoint rules. Urls are already normalized by the curl + * URL parser */ if(curlx_str_cmp(service_name, "s3") || - curlx_str_cmp(service_name, "s3-express") || - curlx_str_cmp(service_name, "s3-outposts")) { + curlx_str_cmp(service_name, "s3express") || + curlx_str_cmp(service_name, "s3-outposts") || + curlx_str_cmp(service_name, "s3-object-lambda")) { return FALSE; } return TRUE; diff --git a/tests/data/Makefile.am b/tests/data/Makefile.am index 5ab507ea5c77..a76a24aeb139 100644 --- a/tests/data/Makefile.am +++ b/tests/data/Makefile.am @@ -1986,6 +1986,7 @@ TESTCASES = \ test2413 \ test2414 \ test2415 \ + test2418 \ test2429 \ test2453 \ \ diff --git a/tests/data/test2418 b/tests/data/test2418 new file mode 100644 index 000000000000..7c9e15f5f7f1 --- /dev/null +++ b/tests/data/test2418 @@ -0,0 +1,53 @@ + + + + +HTTP +aws-sigv4 + + + +# Server-side + + +HTTP/1.1 200 OK +Date: Tue, 09 Nov 2010 14:49:00 GMT +Server: test-server/fake +Content-Length: 6 +Content-Type: text/html + +-foo- + + + +# Client-side + + +http + + +Debug +aws + + +aws-sigv4 for s3express does not encode the path again + + +"http://fake.fake.fake:8000/%TESTNUMBER/a%23b.txt" -u user:secret --aws-sigv4 "aws:amz:us-east-2:s3express" -H "x-amz-content-sha256: UNSIGNED-PAYLOAD" --connect-to fake.fake.fake:8000:%HOSTIP:%HTTPPORT + + + +# Verify data after the test has been "shot" + + +GET /%TESTNUMBER/a%23b.txt HTTP/1.1 +Host: fake.fake.fake:8000 +Authorization: AWS4-HMAC-SHA256 Credential=user/19700101/us-east-2/s3express/aws4_request, SignedHeaders=host;x-amz-content-sha256;x-amz-date, Signature=5ba09dfca3e962e9dabf641f56c81293536c4832e39ccdb0080a30355ff38d64 +X-Amz-Date: 19700101T000000Z +User-Agent: curl/%VERSION +Accept: */* +x-amz-content-sha256: UNSIGNED-PAYLOAD + + + + From 7007a02f39a74a64bba71223b23519a0bb7b99b9 Mon Sep 17 00:00:00 2001 From: Viktor Szakats Date: Sun, 27 Sep 2026 19:06:05 +0200 Subject: [PATCH 275/481] GHA/windows: try fixing mingw64 diffutils deleted upstream The mingw64 (x86_64) environment is in 'legacy' status upstream. Fixing: ``` C:\Windows\system32\cmd.exe /D /S /C D:\a\_temp\setup-msys2\msys2.cmd -c "'pacman' '--noconfirm' '-S' '--needed' '--overwrite' '*' 'mingw-w64-x86_64-cc' 'mingw-w64-x86_64-cmake' 'mingw-w64-x86_64-diffutils' 'mingw-w64-x86_64-libpsl' 'mingw-w64-x86_64-libssh2' 'mingw-w64-x86_64-wolfssl'" error: target not found: mingw-w64-x86_64-diffutils ``` Ref: https://github.com/curl/curl/actions/runs/36335364009/job/108665092309?pr=23122#step:4:278 Refs: https://packages.msys2.org/base/mingw-w64-diffutils https://www.msys2.org/docs/environments/#__tabbed_1_2 https://github.com/msys2/MINGW-packages/commit/5b764a2cf6e110b3a7de002766da854cfc30b9b2 https://github.com/msys2/MINGW-packages/pull/31968 Follow-up to a55dfa5e3564c16d26ea63092bddefbb28777815 #22242 Closes #23123 --- .github/workflows/windows.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 599998a3b2ed..73c4bccd478f 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -359,9 +359,9 @@ jobs: msystem: ${{ matrix.sys }} update: ${{ contains(matrix.install, 'wolfssl') && 'true' || 'false' }} install: >- + diffutils mingw-w64-${{ matrix.env }}-cc mingw-w64-${{ matrix.env }}-${{ matrix.build }} ${{ matrix.build == 'autotools' && 'make' || '' }} - ${{ matrix.sys != 'mingw32' && format('mingw-w64-{0}-diffutils', matrix.env) || '' }} mingw-w64-${{ matrix.env }}-libpsl ${{ matrix.install }} From cee47ee33b8d98bfd95c343ccc424d4955e9de47 Mon Sep 17 00:00:00 2001 From: Viktor Szakats Date: Sun, 27 Sep 2026 18:35:49 +0200 Subject: [PATCH 276/481] cmake: link llvm stdc++ lib to AWS-LC/BoringSSL-specific feature detections only When using llvm/clang combined with AWS-LC/BoringSSL and `OPENSSL_USE_STATIC_LIBS=ON`. Passing the stdc++ library to other feature detections may break them, ending the configure stage with a weird error. As in this example seen with quiche 0.30.0 (+ cmake 4.4.3) on Linux: ``` [...] -- Performing Test HAVE_FSETXATTR_5 CMake Error at curl/bld/CMakeFiles/CMakeTmp/CMakeLists.txt:30 (target_link_libraries): Target "cmTC_503bd" links to: Threads::Threads but the target was not found. Possible reasons include: * There is a typo in the target name. * A find_package call is missing for an IMPORTED target. * An ALIAS target is missing. CMake Error at CMake/Macros.cmake:44 (try_compile): Failed to generate test project build system. Call Stack (most recent call first): CMakeLists.txt:1700 (curl_internal_test) ``` Ref: https://github.com/curl/curl/actions/runs/36243669911/job/108408815423#step:21:1729 `CMakeFiles/CMakeConfigureLog.yaml` shows no error message, just a failure with exit code 1. Follow-up to 12a6de2f660dd692cce93cb65ce6e3ec126bb531 #16043 Cherry-picked from #23088 Closes #23122 --- CMakeLists.txt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 38af2de7ebd4..d003ba8abbd4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -848,7 +848,6 @@ if(CURL_USE_OPENSSL) endif() if(OPENSSL_USE_STATIC_LIBS AND CMAKE_C_COMPILER_ID MATCHES "Clang") list(APPEND CURL_LIBS "stdc++") - list(APPEND CMAKE_REQUIRED_LIBRARIES "stdc++") endif() endif() @@ -996,6 +995,9 @@ macro(curl_openssl_check_exists) if(WIN32) list(APPEND CMAKE_REQUIRED_LIBRARIES "bcrypt") # for OpenSSL/LibreSSL BCryptGenRandom() endif() + if((HAVE_AWSLC OR HAVE_BORINGSSL) AND OPENSSL_USE_STATIC_LIBS AND CMAKE_C_COMPILER_ID MATCHES "Clang") + list(APPEND CMAKE_REQUIRED_LIBRARIES "stdc++") + endif() endif() if(USE_WOLFSSL) list(APPEND CMAKE_REQUIRED_LIBRARIES CURL::wolfssl) From bfd313e7a3c94277e481b9ff0f5f2ddc612bb9d1 Mon Sep 17 00:00:00 2001 From: Viktor Szakats Date: Sun, 27 Sep 2026 19:30:46 +0200 Subject: [PATCH 277/481] GHA/linux: switch two remaining valgrind jobs to arm64 runners (for ~2x speed) Almost 2x performance improvement. Saving 8-9 minutes. - CM openssl -O3 libssh valgrind 1: 10m15s -> 5m53s - CM openssl -O3 libssh valgrind 2: 9m31s -> 5m7s Before: https://github.com/curl/curl/actions/runs/36337098108 After: https://github.com/curl/curl/actions/runs/36337357604?pr=23125 Follow-up to 5f921cb948961a12302a484bf47cac3d0818beba #23079 Follow-up to ab8ccaed2479bf7d019b3aa25f22299546e23828 #20392 Follow-up to 2b0d8dcc16c531d3154ab54347a3eaabf9bd2c7d #20231 Closes #23125 --- .github/workflows/linux.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 2e488a41bd25..d7059a93e2bc 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -251,14 +251,14 @@ jobs: configure: CFLAGS=-std=gnu89 --with-openssl --with-libssh2 --enable-debug --disable-verbose - name: 'openssl -O3 libssh valgrind 1' - image: ubuntu-24.04 + image: ubuntu-24.04-arm install_packages: libssh-dev valgrind CFLAGS: -O3 tflags: '--subset=0/2' generate: -DENABLE_DEBUG=ON -DCURL_USE_LIBSSH=ON -DCMAKE_UNITY_BUILD_BATCH_SIZE=50 -DCURL_ENABLE_NTLM=ON - name: 'openssl -O3 libssh valgrind 2' - image: ubuntu-24.04 + image: ubuntu-24.04-arm install_packages: libssh-dev valgrind CFLAGS: -O3 tflags: '--subset=1/2' From 9c9331604083048cb7e71d678c7ba11125cff727 Mon Sep 17 00:00:00 2001 From: Viktor Szakats Date: Thu, 24 Sep 2026 23:53:37 +0200 Subject: [PATCH 278/481] quiche: fix building with quiche 0.30.0 in CI, update/extend build instructions - docs/HTTP3.md: update instructions for quiche and curl with autotools. - docs/HTTP3.md: add instructions for curl with CMake. - GHA/http3-linux: bump to quiche 0.30.0 - GHA/http3-linux: adjust local script for BoringSSL sources that changed location upstream. - GHA/http3-linux: use clang in quiche jobs to stay in sync with the quiche build also using clang. Necessary to avoid C++ standard library confusion. - GHA/http3-linux: explicitly pass `stdc++` library for BoringSSL in quiche jobs. - GHA/http3-linux: pass `-fPIC`, to correctly build against BoringSSL. - GHA/http3-linux: pass `-DOPENSSL_USE_STATIC_LIBS=ON` to CMake to enable `stdc++`. - GHA/http3-linux: uninstall `libssl-dev` package to avoid CMake picking it up, and ending up with confused OpenSSL target settings (consisting of the manual configuration intermixed with system openssl.pc settings) that make the configure stage fail. Follow-up to cee47ee33b8d98bfd95c343ccc424d4955e9de47 #23122 Follow-up to 47c08c8282aba97ffdaf3095b713620c83465c5f #23116 Follow-up to efc5e67bda67b9573694e820cc8c1273f01ce937 #22988 Closes #23088 --- .github/workflows/http3-linux.yml | 16 ++++++++++++---- docs/HTTP3.md | 20 ++++++++++++++------ 2 files changed, 26 insertions(+), 10 deletions(-) diff --git a/.github/workflows/http3-linux.yml b/.github/workflows/http3-linux.yml index 50efeb3094b2..00d5c2fc5daa 100644 --- a/.github/workflows/http3-linux.yml +++ b/.github/workflows/http3-linux.yml @@ -55,7 +55,7 @@ env: OPENSSL_PREV_VERSION: 3.6.2 OPENSSL_PREV_SHA256: aaf51a1fe064384f811daeaeb4ec4dce7340ec8bd893027eee676af31e83a04f # renovate: datasource=github-tags depName=cloudflare/quiche versioning=semver registryUrl=https://github.com - QUICHE_VERSION: 0.29.3 + QUICHE_VERSION: 0.30.0 # renovate: datasource=github-tags depName=wolfSSL/wolfssl versioning=semver extractVersion=^v?(?.+)-stable$ registryUrl=https://github.com WOLFSSL_VERSION: 5.9.4 # renovate: datasource=github-tags depName=ngtcp2/nghttp3 versioning=semver registryUrl=https://github.com @@ -539,8 +539,11 @@ jobs: -DCURL_DISABLE_LDAP=ON -DCURL_DISABLE_HTTPSIG=OFF - name: 'quiche' + install_packages: clang install_steps: skipall - LDFLAGS: -Wl,-rpath,/home/runner/quiche/target/release + CC: clang + CFLAGS: -fPIC + LDFLAGS: -Wl,-rpath,/home/runner/quiche/target/release -lstdc++ PKG_CONFIG_PATH: /home/runner/nghttp2/build/lib/pkgconfig configure: >- --with-openssl=/home/runner/quiche/boringssl @@ -549,9 +552,11 @@ jobs: --enable-unity - name: 'quiche' + install_packages: clang + CC: clang PKG_CONFIG_PATH: /home/runner/nghttp2/build/lib/pkgconfig:/home/runner/quiche/target/release generate: >- - -DOPENSSL_ROOT_DIR=/home/runner/quiche/boringssl + -DOPENSSL_ROOT_DIR=/home/runner/quiche/boringssl -DOPENSSL_USE_STATIC_LIBS=ON -DUSE_QUICHE=ON -DCURL_CA_FALLBACK=ON -DCURL_DISABLE_HTTPSIG=OFF @@ -756,7 +761,7 @@ jobs: cd .. mkdir -p quiche/boringssl/lib find quiche/target/release \( -name libcrypto.a -o -name libssl.a \) -exec ln -vnf -- '{}' quiche/boringssl/lib \; - find quiche/target/release/build/boring-sys-*/out/boringssl/src -maxdepth 1 \( -name include \) -exec ln -vsf -- '../../{}' quiche/boringssl \; + find quiche/target/release/build/boring-sys-*/out/boringssl -maxdepth 1 \( -name include \) -exec ln -vsf -- '../../{}' quiche/boringssl \; # Reduce cached blob size rm -rf quiche/target/release/build/boring-sys-*/out/build/CMakeFiles rm -rf quiche/target/release/deps @@ -777,6 +782,8 @@ jobs: - name: 'configure' env: + CC: '${{ matrix.build.CC }}' + CFLAGS: '${{ matrix.build.CFLAGS }}' LDFLAGS: '${{ matrix.build.LDFLAGS }}' MATRIX_NAME: '${{ matrix.build.name }}' MATRIX_CONFIGURE: '${{ matrix.build.configure }}' @@ -785,6 +792,7 @@ jobs: run: | [ -n "${MATRIX_PKG_CONFIG_PATH}" ] && export PKG_CONFIG_PATH="${MATRIX_PKG_CONFIG_PATH}" if [ "${MATRIX_BUILD}" = 'cmake' ]; then + [[ "${MATRIX_NAME}" = *'quiche'* ]] && sudo apt-get -o Dpkg::Use-Pty=0 purge libssl-dev [[ "${MATRIX_NAME}" = *'boringssl'* ]] && options=" -DBORINGSSL_VERSION=${BORINGSSL_VERSION}" cmake -B bld -G Ninja \ -DCMAKE_C_COMPILER_TARGET="$(uname -m)-unknown-linux-gnu" -DBUILD_STATIC_LIBS=ON \ diff --git a/docs/HTTP3.md b/docs/HTTP3.md index 07535f1559fa..a7cc774d765c 100644 --- a/docs/HTTP3.md +++ b/docs/HTTP3.md @@ -215,28 +215,36 @@ but in case of problems, we recommend their latest release tag. ## Build -Build quiche and BoringSSL (described here for quiche v0.29.1, the locations +Build quiche and BoringSSL (described here for quiche v0.30.0, the locations where BoringSSL is to be found vary with version): - % git clone --depth 1 --branch 0.29.1 --recursive https://github.com/cloudflare/quiche + % git clone --depth 1 --branch 0.30.0 --recursive https://github.com/cloudflare/quiche % cd quiche % cargo build --package quiche --release --features ffi,pkg-config-meta,qlog % ln -s libquiche.so target/release/libquiche.so.0 % mkdir -p boringssl/lib % find target/release \( -name libcrypto.a -o -name libssl.a \) -exec ln -vnf -- '{}' boringssl/lib \; - % find target/release/build/boring-sys-*/out/boringssl/src -maxdepth 1 \( -name include \) -exec ln -vsf -- '../{}' boringssl \; + % find target/release/build/boring-sys-*/out/boringssl -maxdepth 1 \( -name include \) -exec ln -vsf -- '../{}' boringssl \; -Build curl: +Build curl (with autotools): % cd .. % git clone --depth 1 https://github.com/curl/curl % cd curl % autoreconf -fi - % ./configure --with-openssl=$PWD/../quiche/boringssl --with-quiche=$PWD/../quiche/target/release + % CC=clang ./configure --with-openssl=$PWD/../quiche/boringssl --with-quiche=$PWD/../quiche/target/release CFLAGS=-fPIC LDFLAGS=-lstdc++ % make % make install -If `make install` results in `Permission denied` error, you need to prepend +Build curl (with CMake): + + % cd .. + % git clone --depth 1 https://github.com/curl/curl + % cd curl + % PKG_CONFIG_PATH=$PWD/../quiche/target/release CC=clang cmake -B bld -DOPENSSL_ROOT_DIR=$PWD/../quiche/boringssl -DOPENSSL_USE_STATIC_LIBS=ON -DUSE_QUICHE=ON + % cmake --build bld + +If the install step results in `Permission denied` error, you need to prepend it with `sudo`. # `--http3` From b3640b0bf71cc7af86544e3893fc5392292457da Mon Sep 17 00:00:00 2001 From: Viktor Szakats Date: Sun, 27 Sep 2026 19:22:27 +0200 Subject: [PATCH 279/481] GHA/windows: move Linux cross-builds to arm64 runners For slightly better performance, and better efficiency. Saving 1-2 minutes. - autotools: 2m20s -> 1m44s - cmake: 1m14s -> 55s - cmake clang-tidy: 4m30s -> 3m49s Also: - GHA/windows: fix `windres` command-line for non-intel machines. Before: https://github.com/curl/curl/actions/runs/36336328672 After: https://github.com/curl/curl/actions/runs/36344305212?pr=23124 Closes #23124 --- .github/workflows/windows.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 73c4bccd478f..b0264761f95d 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -795,7 +795,7 @@ jobs: linux-cross-mingw-w64: name: "linux-mingw, ${{ matrix.build == 'cmake' && 'CM' || 'AM' }} ${{ matrix.compiler }}" - runs-on: ubuntu-26.04 + runs-on: ubuntu-26.04-arm timeout-minutes: 10 cache-mode: none env: @@ -836,6 +836,7 @@ jobs: options+=' -DENABLE_UNICODE=ON -DUSE_SSLS_EXPORT=ON' options+=' -DCMAKE_C_COMPILER=clang-22' options+=" -DCMAKE_RC_COMPILER=llvm-windres-$(clang-22 -dumpversion | cut -d '.' -f 1)" + options+=' -DCMAKE_RC_FLAGS=--target=pe-x86-64' # Required on ubuntu-26.04-arm else options+=" -DCMAKE_C_COMPILER=${TRIPLET}-gcc" fi From b2799a4bf3e36c91de0b766bc4d9a0ac4a9f1ec7 Mon Sep 17 00:00:00 2001 From: Viktor Szakats Date: Mon, 28 Sep 2026 08:58:53 +0200 Subject: [PATCH 280/481] schannel: fix `-Wextra-semi-stmt` with clang and extra debug enabled Fixing: ``` lib/vtls/schannel.c:1881:33: error: empty expression statement has no effect; remove unnecessary ';' to silence this warning [-Wextra-semi-stmt] 1881 | SCH_DEV_SHOWBOOL(block_read); | ^ lib/vtls/schannel.c:1882:34: error: empty expression statement has no effect; remove unnecessary ';' to silence this warning [-Wextra-semi-stmt] 1882 | SCH_DEV_SHOWBOOL(block_write); | ^ lib/vtls/schannel.c:1883:31: error: empty expression statement has no effect; remove unnecessary ';' to silence this warning [-Wextra-semi-stmt] 1883 | SCH_DEV_SHOWBOOL(blocking); | ^ ``` Ref: https://github.com/curl/curl/actions/runs/36388449021/job/108818797981?pr=23128#step:11:35 Follow-up to ac7ae08f0259a2e956d805b6d769d498fb5044be #14826 Closes #23129 --- lib/vtls/schannel.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/vtls/schannel.c b/lib/vtls/schannel.c index c5713b6f5874..8ffe2db1f6e3 100644 --- a/lib/vtls/schannel.c +++ b/lib/vtls/schannel.c @@ -61,8 +61,10 @@ */ #ifdef CURL_SCHANNEL_DEV_DEBUG #define SCH_DEV(x) x -#define SCH_DEV_SHOWBOOL(x) \ - infof(data, "schannel: " #x " %s", (x) ? "TRUE" : "FALSE"); +#define SCH_DEV_SHOWBOOL(x) \ + do { \ + infof(data, "schannel: " #x " %s", (x) ? "TRUE" : "FALSE"); \ + } while(0) #else #define SCH_DEV(x) do {} while(0) #define SCH_DEV_SHOWBOOL(x) do {} while(0) From e5218eeab6da84f003a7c672cc6c62e3b44a3dea Mon Sep 17 00:00:00 2001 From: Viktor Szakats Date: Mon, 28 Sep 2026 09:11:41 +0200 Subject: [PATCH 281/481] GHA/windows: drop redundant verbose build option The build step was subsequently made verbose for all jobs. Also `-DCMAKE_VERBOSE_MAKEFILE=ON` should only affect the Makefile generator, not Ninja as used in these jobs. Ref: https://cmake.org/cmake/help/latest/variable/CMAKE_VERBOSE_MAKEFILE.html Follow-up to 17a669426f36b467dfd945b4b35f6211598b7977 #17537 Follow-up to ac7ae08f0259a2e956d805b6d769d498fb5044be #14826 Closes #23130 --- .github/workflows/windows.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index b0264761f95d..61b9661430b0 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -325,7 +325,7 @@ jobs: # install: 'mingw-w64-ucrt-x86_64-libssh2' } - { name: 'schannel dev debug', type: 'Debug', cppflags: '-DCURL_SCHANNEL_DEV_DEBUG', image: 'windows-2025', build: 'cmake' , sys: 'mingw64' , env: 'x86_64' , tflags: 'skiprun', - config: '-DENABLE_DEBUG=ON -DBUILD_SHARED_LIBS=ON -DCURL_USE_SCHANNEL=ON -DCURL_USE_WOLFSSL=ON -DENABLE_UNICODE=ON -DCMAKE_VERBOSE_MAKEFILE=ON -DCURL_DISABLE_HTTPSIG=OFF', + config: '-DENABLE_DEBUG=ON -DBUILD_SHARED_LIBS=ON -DCURL_USE_SCHANNEL=ON -DCURL_USE_WOLFSSL=ON -DENABLE_UNICODE=ON -DCURL_DISABLE_HTTPSIG=OFF', install: 'mingw-w64-x86_64-libssh2 mingw-w64-x86_64-wolfssl' } - { name: 'MultiSSL R', type: 'Release', build: 'cmake' , sys: 'mingw32' , env: 'i686' , tflags: 'skiprun', From 29dfc2abb03681d9f9ce77331fbf81de919ef346 Mon Sep 17 00:00:00 2001 From: Viktor Szakats Date: Mon, 28 Sep 2026 08:49:27 +0200 Subject: [PATCH 282/481] GHA/windows: replace legacy mingw64 jobs with ucrt64 and clang64 There remains one mingw32 job, which is still useful to reveal Windows 32-bit build issues. mingw64 remains tested in "dl-mingw" jobs. Ref: https://www.msys2.org/news/#2026-03-15-deprecating-the-mingw64-environment Closes #23128 --- .github/workflows/windows.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 61b9661430b0..d81704c0a4cb 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -278,17 +278,17 @@ jobs: config: '--with-openssl --enable-ntlm', install: 'openssl-devel libssh2-devel' } # MinGW - { name: 'default', - build: 'autotools', sys: 'mingw64' , env: 'x86_64' , tflags: 'skiprun', + build: 'autotools', sys: 'clang64' , env: 'clang-x86_64' , tflags: 'skiprun', config: '--enable-debug --with-openssl --disable-threaded-resolver --enable-static --without-zlib', - install: 'mingw-w64-x86_64-openssl mingw-w64-x86_64-libssh2' } + install: 'mingw-w64-clang-x86_64-openssl mingw-w64-clang-x86_64-libssh2' } - { name: 'wolfssl c-ares U', build: 'autotools', sys: 'ucrt64' , env: 'ucrt-x86_64' , tflags: '', config: '--enable-debug --with-wolfssl --enable-windows-unicode --enable-ares --enable-static --disable-shared --enable-ca-native --enable-ntlm', install: 'mingw-w64-ucrt-x86_64-c-ares mingw-w64-ucrt-x86_64-nghttp3 mingw-w64-ucrt-x86_64-libssh2 mingw-w64-ucrt-x86_64-wolfssl' } - { name: 'schannel c-ares U', type: 'Debug', - build: 'cmake' , sys: 'mingw64' , env: 'x86_64' , tflags: '--min=1720', + build: 'cmake' , sys: 'clang64' , env: 'clang-x86_64' , tflags: '--min=1720', config: '-DENABLE_DEBUG=ON -DBUILD_SHARED_LIBS=OFF -DCURL_USE_SCHANNEL=ON -DENABLE_UNICODE=ON -DENABLE_ARES=ON -DCURL_DROP_UNUSED=ON', - install: 'mingw-w64-x86_64-c-ares mingw-w64-x86_64-libssh2' } + install: 'mingw-w64-clang-x86_64-c-ares mingw-w64-clang-x86_64-libssh2' } # MinGW torture - { name: 'schannel U torture 1', type: 'Debug', build: 'cmake' , sys: 'ucrt64' , env: 'ucrt-x86_64' , tflags: '-t --shallow=13 --subset=0/2', @@ -324,9 +324,9 @@ jobs: # config: '--without-debug --with-schannel --disable-static', # install: 'mingw-w64-ucrt-x86_64-libssh2' } - { name: 'schannel dev debug', type: 'Debug', cppflags: '-DCURL_SCHANNEL_DEV_DEBUG', image: 'windows-2025', - build: 'cmake' , sys: 'mingw64' , env: 'x86_64' , tflags: 'skiprun', + build: 'cmake' , sys: 'ucrt64' , env: 'ucrt-x86_64' , tflags: 'skiprun', config: '-DENABLE_DEBUG=ON -DBUILD_SHARED_LIBS=ON -DCURL_USE_SCHANNEL=ON -DCURL_USE_WOLFSSL=ON -DENABLE_UNICODE=ON -DCURL_DISABLE_HTTPSIG=OFF', - install: 'mingw-w64-x86_64-libssh2 mingw-w64-x86_64-wolfssl' } + install: 'mingw-w64-ucrt-x86_64-libssh2 mingw-w64-ucrt-x86_64-wolfssl' } - { name: 'MultiSSL R', type: 'Release', build: 'cmake' , sys: 'mingw32' , env: 'i686' , tflags: 'skiprun', config: '-DENABLE_DEBUG=OFF -DBUILD_SHARED_LIBS=ON -DCURL_USE_GNUTLS=ON -DCURL_USE_OPENSSL=ON -DCURL_USE_SCHANNEL=ON -DENABLE_ARES=ON -DENABLE_UNICODE=ON', From 484e4e9f3ea1481f471549690e189fc08184f753 Mon Sep 17 00:00:00 2001 From: Viktor Szakats Date: Mon, 28 Sep 2026 00:15:41 +0200 Subject: [PATCH 283/481] GHA/macos: add workaround to pass OS target version in GCC cmake jobs CMake tries and incorrectly fails to detect that GCC supports the `-mmacosx-version-min=` option. This results in missing to automatically forward the `CMAKE_OSX_DEPLOYMENT_TARGET` value. Pass it manually as a workaround. Note that no GCC CI job is passing an OS target version at the moment. The issue came up in a temporary test job. Merge it just in case and to sync with curl-for-win. As of current latest CMake 4.4.3: ``` -- Checking whether C compiler has -isysroot -- Checking whether C compiler has -isysroot - no -- Checking whether C compiler supports OSX deployment target flag -- Checking whether C compiler supports OSX deployment target flag - no ``` (It also incorrectly fails to detect `-isysroot` support.) The reason for the mis-undetection is that CMake is using the `gcc -v --help` command to list available options, then checking for exit code and the output for the strings, but the exit code is `1`, and thus considered a failure, even though the strings are present. The failure comes from: ``` ld: unknown options: --help collect2: error: ld returned 1 exit status ``` Turns out this was fixed in CMake master 4 days ago: https://gitlab.kitware.com/cmake/cmake/-/commit/cc6bcefc6a686a0caea5ea7c4258f3070c4e2983 The bug was introduced in 4.4.0 via: https://gitlab.kitware.com/cmake/cmake/-/commit/a1b5c0da877f78db3e5dfc8d9ec4f9dac21f7a5c Cherry-picked from #23126 Closes #23131 --- .github/workflows/macos.yml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index 19ee69eddea9..57f69da6d456 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -458,6 +458,8 @@ jobs: CC+=" --target=$(uname -m)-apple-darwin" fi + [ -n "${MATRIX_MACOS_VERSION_MIN}" ] && CFLAGS+=" -mmacosx-version-min=${MATRIX_MACOS_VERSION_MIN}" # for autotools, and cmake with gcc + if [ "${MATRIX_BUILD}" = 'cmake' ]; then for _chkprefill in '' ${MATRIX_CHKPREFILL}; do options='' @@ -483,7 +485,6 @@ jobs: options+=" --with-sysroot=${sysroot}" CFLAGS+=" --sysroot=${sysroot}" fi - [ -n "${MATRIX_MACOS_VERSION_MIN}" ] && CFLAGS+=" -mmacosx-version-min=${MATRIX_MACOS_VERSION_MIN}" [[ "${MATRIX_INSTALL_STEPS}" = *'pytest'* ]] && options+=' --with-test-vsftpd=no' # Skip ~20 tests that stretch run time by 7x on macOS mkdir bld && cd bld && ../configure --prefix="$PWD"/curl-install --enable-unity --enable-warnings --enable-werror --disable-static \ --disable-dependency-tracking --enable-option-checking=fatal \ @@ -707,6 +708,9 @@ jobs: CC+=" --target=$(uname -m)-apple-darwin" fi + export CFLAGS + [ -n "${MATRIX_MACOS_VERSION_MIN}" ] && CFLAGS+=" -mmacosx-version-min=${MATRIX_MACOS_VERSION_MIN}" # for autotools, and cmake with gcc + if [ "${MATRIX_BUILD}" = 'cmake' ]; then [ -n "${MATRIX_MACOS_VERSION_MIN}" ] && options+=" -DCMAKE_OSX_DEPLOYMENT_TARGET=${MATRIX_MACOS_VERSION_MIN}" # would pick up nghttp2, libidn2, and libssh2 @@ -723,7 +727,6 @@ jobs: -DCURL_USE_GSSAPI=ON -DCURL_GSS_FLAVOR=Apple \ ${options} else - export CFLAGS if [[ "${MATRIX_COMPILER}" = 'llvm'* ]]; then options+=" --target=$(uname -m)-apple-darwin" fi @@ -731,7 +734,6 @@ jobs: options+=" --with-sysroot=${sysroot}" CFLAGS+=" --sysroot=${sysroot}" fi - [ -n "${MATRIX_MACOS_VERSION_MIN}" ] && CFLAGS+=" -mmacosx-version-min=${MATRIX_MACOS_VERSION_MIN}" # would pick up nghttp2, libidn2, but libssh2 is disabled by default mkdir bld && cd bld && ../configure --enable-unity --enable-warnings --enable-werror --disable-static \ --disable-dependency-tracking --enable-option-checking=fatal \ From c492678e3d80581231e1b5829cf9ad39f04eadbd Mon Sep 17 00:00:00 2001 From: Viktor Szakats Date: Mon, 28 Sep 2026 11:29:03 +0200 Subject: [PATCH 284/481] apple: sync up version guard macro style - lib/vtls/apple: use double underscores for `MAC_OS_X_VERSION_*` macros to sync with rest of code. The difference (if any) is undocumented and muddy. iPhone/TV/etc OS macros only come in `__`-prefixed flavor; use it for all. - lib/vtls/apple: use irregular indent to match rest of code. (and to improve readability) - lib/vtls/apple: use irregular `\` alignment to match rest of code. - lib: drop parentheses to match lib/vtls/apple. Closes #23133 --- lib/md4.c | 8 ++++---- lib/md5.c | 8 ++++---- lib/sha256.c | 4 ++-- lib/vtls/apple.c | 24 ++++++++++++------------ 4 files changed, 22 insertions(+), 22 deletions(-) diff --git a/lib/md4.c b/lib/md4.c index 06cfbb0b3e4a..04773c1e96c6 100644 --- a/lib/md4.c +++ b/lib/md4.c @@ -100,13 +100,13 @@ static void my_md4_final(unsigned char *digest, my_md4_ctx *ctx) } #elif (defined(__MAC_OS_X_VERSION_MAX_ALLOWED) && \ - (__MAC_OS_X_VERSION_MAX_ALLOWED >= 1040) && \ + __MAC_OS_X_VERSION_MAX_ALLOWED >= 1040 && \ defined(__MAC_OS_X_VERSION_MIN_REQUIRED) && \ - (__MAC_OS_X_VERSION_MIN_REQUIRED < 101500)) || \ + __MAC_OS_X_VERSION_MIN_REQUIRED < 101500) || \ (defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && \ - (__IPHONE_OS_VERSION_MAX_ALLOWED >= 20000) && \ + __IPHONE_OS_VERSION_MAX_ALLOWED >= 20000 && \ defined(__IPHONE_OS_VERSION_MIN_REQUIRED) && \ - (__IPHONE_OS_VERSION_MIN_REQUIRED < 130000)) + __IPHONE_OS_VERSION_MIN_REQUIRED < 130000) #include typedef CC_MD4_CTX my_md4_ctx; diff --git a/lib/md5.c b/lib/md5.c index 42f5d04e889e..2f0511712f33 100644 --- a/lib/md5.c +++ b/lib/md5.c @@ -158,13 +158,13 @@ static void my_md5_final(unsigned char *digest, void *ctx) } #elif (defined(__MAC_OS_X_VERSION_MAX_ALLOWED) && \ - (__MAC_OS_X_VERSION_MAX_ALLOWED >= 1040) && \ + __MAC_OS_X_VERSION_MAX_ALLOWED >= 1040 && \ defined(__MAC_OS_X_VERSION_MIN_REQUIRED) && \ - (__MAC_OS_X_VERSION_MIN_REQUIRED < 101500)) || \ + __MAC_OS_X_VERSION_MIN_REQUIRED < 101500) || \ (defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && \ - (__IPHONE_OS_VERSION_MAX_ALLOWED >= 20000) && \ + __IPHONE_OS_VERSION_MAX_ALLOWED >= 20000 && \ defined(__IPHONE_OS_VERSION_MIN_REQUIRED) && \ - (__IPHONE_OS_VERSION_MIN_REQUIRED < 130000)) + __IPHONE_OS_VERSION_MIN_REQUIRED < 130000) #include /* For Apple operating systems: CommonCrypto has the functions we need. diff --git a/lib/sha256.c b/lib/sha256.c index 89b96017bdc2..d6de41a08bb7 100644 --- a/lib/sha256.c +++ b/lib/sha256.c @@ -170,9 +170,9 @@ static void my_sha256_final(unsigned char *digest, void *ctx) } #elif (defined(__MAC_OS_X_VERSION_MAX_ALLOWED) && \ - (__MAC_OS_X_VERSION_MAX_ALLOWED >= 1040)) || \ + __MAC_OS_X_VERSION_MAX_ALLOWED >= 1040) || \ (defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && \ - (__IPHONE_OS_VERSION_MAX_ALLOWED >= 20000)) + __IPHONE_OS_VERSION_MAX_ALLOWED >= 20000) #include typedef CC_SHA256_CTX my_sha256_ctx; diff --git a/lib/vtls/apple.c b/lib/vtls/apple.c index 45acbb3b7c2d..9959304f19e3 100644 --- a/lib/vtls/apple.c +++ b/lib/vtls/apple.c @@ -50,18 +50,18 @@ #include -#if (defined(MAC_OS_X_VERSION_MAX_ALLOWED) && \ - MAC_OS_X_VERSION_MAX_ALLOWED >= 101400) || \ - (defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && \ - __IPHONE_OS_VERSION_MAX_ALLOWED >= 120000) +#if (defined(__MAC_OS_X_VERSION_MAX_ALLOWED) && \ + __MAC_OS_X_VERSION_MAX_ALLOWED >= 101400) || \ + (defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && \ + __IPHONE_OS_VERSION_MAX_ALLOWED >= 120000) #define SUPPORTS_SecTrustEvaluateWithError 1 #endif #if defined(SUPPORTS_SecTrustEvaluateWithError) && \ - ((defined(MAC_OS_X_VERSION_MIN_REQUIRED) && \ - MAC_OS_X_VERSION_MIN_REQUIRED >= 101400) || \ - (defined(__IPHONE_OS_VERSION_MIN_REQUIRED) && \ - __IPHONE_OS_VERSION_MIN_REQUIRED >= 120000)) + ((defined(__MAC_OS_X_VERSION_MIN_REQUIRED) && \ + __MAC_OS_X_VERSION_MIN_REQUIRED >= 101400) || \ + (defined(__IPHONE_OS_VERSION_MIN_REQUIRED) && \ + __IPHONE_OS_VERSION_MIN_REQUIRED >= 120000)) #define REQUIRES_SecTrustEvaluateWithError 1 #endif @@ -71,10 +71,10 @@ #undef SUPPORTS_SecTrustEvaluateWithError #endif -#if (defined(MAC_OS_X_VERSION_MAX_ALLOWED) && \ - MAC_OS_X_VERSION_MAX_ALLOWED >= 100900) || \ - (defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && \ - __IPHONE_OS_VERSION_MAX_ALLOWED >= 70000) +#if (defined(__MAC_OS_X_VERSION_MAX_ALLOWED) && \ + __MAC_OS_X_VERSION_MAX_ALLOWED >= 100900) || \ + (defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && \ + __IPHONE_OS_VERSION_MAX_ALLOWED >= 70000) #define SUPPORTS_SecOCSP 1 #endif From c2a94ba9ba70b4b6378b18cb3f04504162542723 Mon Sep 17 00:00:00 2001 From: Viktor Szakats Date: Thu, 24 Sep 2026 21:07:19 +0200 Subject: [PATCH 285/481] socketpair: use `pipe2()` on Apple OSes (requires SDK 27) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `pipe2()` became available with Apple OS 27. It requires SDK 27 to build for. Use it in curl unconditionally if targeting 27+, and use it dynamically otherwise if the runtime environment supports it. Also: - GHA/macos: enable the latter, common use-case in CI. - Tested all combinations in temporary CI runs. Ref: https://developer.apple.com/documentation/system/filedescriptor/pipe(options:) Reported-by: Alexander Köplinger Ref: https://github.com/curl/curl/issues/21236#issuecomment-5694891476 Follow-up to ed272699f36e5b6826373a84911a3ebfbcf82aac #23087 Follow-up to 1737e47f59d93fa6e2001c2dd3404d67531d0aa9 #21271 #21236 Closes #23126 --- .github/workflows/macos.yml | 3 +++ CMakeLists.txt | 3 ++- configure.ac | 3 ++- lib/curl_setup.h | 24 ++++++++++++++++++++++++ lib/socketpair.c | 23 +++++++++++++++++------ 5 files changed, 48 insertions(+), 8 deletions(-) diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index 57f69da6d456..d043ad20be8a 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -44,6 +44,7 @@ cache-mode: none # - 10.11 El Capitan (2015) - connectx() (runtime) # - 10.12 Sierra (2016) - clock_gettime() (build-time, runtime) # - 10.14 Mojave (2018) - SecTrustEvaluateWithError() (runtime), GSS Framework +# - 27.0 Golden Gate (2026) - pipe2() (build-time, runtime) env: CURL_CI: github @@ -284,6 +285,8 @@ jobs: -DCMAKE_C_STANDARD=90 -DCURL_ENABLE_NTLM=ON -DUSE_PROXY_HTTP3=ON - name: 'OpenSSL SecTrust krb5' + image: xcode-27 # for pipe2() + macos-version-min: '10.15' compiler: clang install: libnghttp3 libngtcp2 install_steps: pytest diff --git a/CMakeLists.txt b/CMakeLists.txt index d003ba8abbd4..79cf731f6535 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1660,9 +1660,10 @@ check_function_exists("setrlimit" HAVE_SETRLIMIT) if(APPLE) check_function_exists("mach_absolute_time" HAVE_MACH_ABSOLUTE_TIME) else() - # Apple platforms do not offer pipe2(), but the iPhone Simulator-specific + # Apple SDK <27 do not offer pipe2(), but the iPhone Simulator-specific # /usr/lib/system/libsystem_sim_kernel.dylib exports it. To avoid false # detection, omit this feature check for Apple targets. + # For 27+, detection is done in-source. check_function_exists("pipe2" HAVE_PIPE2) endif() diff --git a/configure.ac b/configure.ac index a2843b4310e6..49886c535f86 100644 --- a/configure.ac +++ b/configure.ac @@ -4221,9 +4221,10 @@ AC_CHECK_FUNCS([\ ]) if test "$curl_cv_apple" != "yes"; then - dnl Apple platforms do not offer pipe2(), but the iPhone Simulator-specific + dnl Apple SDK <27 do not offer pipe2(), but the iPhone Simulator-specific dnl /usr/lib/system/libsystem_sim_kernel.dylib exports it. To avoid false dnl detection, omit this feature check for Apple targets. + dnl For 27+, detection is done in-source. AC_CHECK_FUNCS([\ pipe2 \ ]) diff --git a/lib/curl_setup.h b/lib/curl_setup.h index 499cdc82da04..50bbbe46b6bb 100644 --- a/lib/curl_setup.h +++ b/lib/curl_setup.h @@ -293,6 +293,30 @@ #include +#if !defined(HAVE_PIPE2) && \ + ((defined(__MAC_OS_X_VERSION_MAX_ALLOWED) && \ + __MAC_OS_X_VERSION_MAX_ALLOWED >= 270000 && \ + defined(__MAC_OS_X_VERSION_MIN_REQUIRED) && \ + __MAC_OS_X_VERSION_MIN_REQUIRED >= 270000) || \ + (defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && \ + __IPHONE_OS_VERSION_MAX_ALLOWED >= 270000 && \ + defined(__IPHONE_OS_VERSION_MIN_REQUIRED) && \ + __IPHONE_OS_VERSION_MIN_REQUIRED >= 270000) || \ + (defined(__TV_OS_VERSION_MAX_ALLOWED) && \ + __TV_OS_VERSION_MAX_ALLOWED >= 270000 && \ + defined(__TV_OS_VERSION_MIN_REQUIRED) && \ + __TV_OS_VERSION_MIN_REQUIRED >= 270000) || \ + (defined(__WATCH_OS_VERSION_MAX_ALLOWED) && \ + __WATCH_OS_VERSION_MAX_ALLOWED >= 270000 && \ + defined(__WATCH_OS_VERSION_MIN_REQUIRED) && \ + __WATCH_OS_VERSION_MIN_REQUIRED >= 270000) || \ + (defined(__VISION_OS_VERSION_MAX_ALLOWED) && \ + __VISION_OS_VERSION_MAX_ALLOWED >= 270000 && \ + defined(__VISION_OS_VERSION_MIN_REQUIRED) && \ + __VISION_OS_VERSION_MIN_REQUIRED >= 270000)) +#define HAVE_PIPE2 +#endif + /* Helper macro to expand and concatenate two macros. * Direct macros concatenation does not work because macros * are not expanded before direct concatenation. diff --git a/lib/socketpair.c b/lib/socketpair.c index d4ac66e95ebe..d408cfdae295 100644 --- a/lib/socketpair.c +++ b/lib/socketpair.c @@ -55,10 +55,23 @@ static int wakeup_eventfd(curl_socket_t socks[2], bool nonblocking) static int wakeup_pipe(curl_socket_t socks[2], bool nonblocking) { #ifdef HAVE_PIPE2 - int flags = nonblocking ? O_NONBLOCK | O_CLOEXEC : O_CLOEXEC; - if(pipe2(socks, flags)) - return -1; -#else + return pipe2(socks, (nonblocking ? O_NONBLOCK : 0) | O_CLOEXEC) ? -1 : 0; +#elif defined(__APPLE__) && defined(HAVE_BUILTIN_AVAILABLE) && \ + ((defined(__MAC_OS_X_VERSION_MAX_ALLOWED) && \ + __MAC_OS_X_VERSION_MAX_ALLOWED >= 270000) || \ + (defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && \ + __IPHONE_OS_VERSION_MAX_ALLOWED >= 270000) || \ + (defined(__TV_OS_VERSION_MAX_ALLOWED) && \ + __TV_OS_VERSION_MAX_ALLOWED >= 270000) || \ + (defined(__WATCH_OS_VERSION_MAX_ALLOWED) && \ + __WATCH_OS_VERSION_MAX_ALLOWED >= 270000) || \ + (defined(__VISION_OS_VERSION_MAX_ALLOWED) && \ + __VISION_OS_VERSION_MAX_ALLOWED >= 270000)) + if(__builtin_available(macOS 27.0, iOS 27.0, tvOS 27.0, watchOS 27.0, + visionOS 27.0, macCatalyst 27.0, *)) + return pipe2(socks, (nonblocking ? O_NONBLOCK : 0) | O_CLOEXEC) ? -1 : 0; + else +#endif if(pipe(socks)) return -1; #ifdef HAVE_FCNTL @@ -79,8 +92,6 @@ static int wakeup_pipe(curl_socket_t socks[2], bool nonblocking) return -1; } } -#endif - return 0; } From c345611dc9d7425fbb3d8e3a3e374d798b2ec53a Mon Sep 17 00:00:00 2001 From: Viktor Szakats Date: Mon, 28 Sep 2026 12:47:54 +0200 Subject: [PATCH 286/481] pytest: silence pytest warnings Fixing: ``` [...] =============================== warnings summary =============================== tests/http/test_02_download.py: 3 warnings tests/http/test_07_upload.py: 2 warnings tests/http/test_08_caddy.py: 2 warnings tests/http/test_09_push.py: 1 warning tests/http/test_10_proxy.py: 3 warnings tests/http/test_11_unix.py: 1 warning tests/http/test_14_auth.py: 1 warning tests/http/test_16_info.py: 1 warning tests/http/test_17_ssl_use.py: 4 warnings tests/http/test_18_methods.py: 1 warning tests/http/test_19_shutdown.py: 2 warnings tests/http/test_20_websockets.py: 4 warnings tests/http/test_21_resolve.py: 4 warnings /Users/runner/venv/lib/python3.14/site-packages/_pytest/fixtures.py:1313: PytestRemovedIn10Warning: Class-scoped fixture defined as instance method is deprecated. Instance attributes set in this fixture will NOT be visible to test methods, as each test gets a new instance while the fixture runs only once per class. Use @classmethod decorator and set attributes on cls instead. See https://docs.pytest.org/en/stable/deprecations.html#class-scoped-fixture-as-instance-method fixturefunc = resolve_fixture_function(fixturedef, request) -- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html =========== 629 passed, 215 skipped, 29 warnings in 80.93s (0:01:20) =========== ``` Ref: https://github.com/curl/curl/actions/runs/36406255757/job/108875619244 Ref: https://docs.pytest.org/en/stable/deprecations.html#class-scoped-fixture-as-instance-method Closes #23135 --- tests/http/test_02_download.py | 3 ++- tests/http/test_07_upload.py | 3 ++- tests/http/test_08_caddy.py | 19 +++++++++++-------- tests/http/test_09_push.py | 3 ++- tests/http/test_10_proxy.py | 3 ++- tests/http/test_11_unix.py | 5 +++-- tests/http/test_14_auth.py | 3 ++- tests/http/test_16_info.py | 3 ++- tests/http/test_17_ssl_use.py | 3 ++- tests/http/test_18_methods.py | 3 ++- tests/http/test_19_shutdown.py | 3 ++- tests/http/test_20_websockets.py | 6 ++++-- tests/http/test_21_resolve.py | 6 ++++-- tests/http/test_22_httpsrr.py | 3 ++- tests/http/test_30_vsftpd.py | 19 +++++++++++-------- tests/http/test_31_vsftpds.py | 17 ++++++++++------- tests/http/test_32_ftps_vsftpd.py | 17 ++++++++++------- tests/http/test_40_socks.py | 6 ++++-- tests/http/test_50_scp.py | 3 ++- tests/http/test_51_sftp.py | 3 ++- 20 files changed, 81 insertions(+), 50 deletions(-) diff --git a/tests/http/test_02_download.py b/tests/http/test_02_download.py index 493d230fda56..39d837795b08 100644 --- a/tests/http/test_02_download.py +++ b/tests/http/test_02_download.py @@ -38,7 +38,8 @@ class TestDownload: @pytest.fixture(autouse=True, scope='class') - def _class_scope(self, env, httpd): + @classmethod + def _class_scope(cls, env, httpd): indir = httpd.docs_dir env.make_data_file(indir=indir, fname="data-0k", fsize=0) env.make_data_file(indir=indir, fname="data-10k", fsize=10 * 1024) diff --git a/tests/http/test_07_upload.py b/tests/http/test_07_upload.py index 8187e0a99bdd..13da59260530 100644 --- a/tests/http/test_07_upload.py +++ b/tests/http/test_07_upload.py @@ -40,7 +40,8 @@ class TestUpload: @pytest.fixture(autouse=True, scope='class') - def _class_scope(self, env, httpd, nghttpx): + @classmethod + def _class_scope(cls, env, httpd, nghttpx): env.make_data_file(indir=env.gen_dir, fname="data-10k", fsize=10 * 1024) env.make_data_file(indir=env.gen_dir, fname="data-63k", fsize=63 * 1024) env.make_data_file(indir=env.gen_dir, fname="data-64k", fsize=64 * 1024) diff --git a/tests/http/test_08_caddy.py b/tests/http/test_08_caddy.py index 467f1cc8308c..09a0be64de60 100644 --- a/tests/http/test_08_caddy.py +++ b/tests/http/test_08_caddy.py @@ -40,13 +40,15 @@ class TestCaddy: @pytest.fixture(autouse=True, scope='class') - def caddy(self, env): + @classmethod + def caddy(cls, env): caddy = Caddy(env=env) assert caddy.initial_start() yield caddy caddy.stop() - def _make_docs_file(self, docs_dir: str, fname: str, fsize: int): + @classmethod + def _make_docs_file(cls, docs_dir: str, fname: str, fsize: int): fpath = os.path.join(docs_dir, fname) data1k = 1024*'x' flen = 0 @@ -57,12 +59,13 @@ def _make_docs_file(self, docs_dir: str, fname: str, fsize: int): return flen @pytest.fixture(autouse=True, scope='class') - def _class_scope(self, env, caddy): - self._make_docs_file(docs_dir=caddy.docs_dir, fname='data10k.data', fsize=10 * 1024) - self._make_docs_file(docs_dir=caddy.docs_dir, fname='data1.data', fsize=1024 * 1024) - self._make_docs_file(docs_dir=caddy.docs_dir, fname='data5.data', fsize=5 * 1024 * 1024) - self._make_docs_file(docs_dir=caddy.docs_dir, fname='data10.data', fsize=10 * 1024 * 1024) - self._make_docs_file(docs_dir=caddy.docs_dir, fname='data100.data', fsize=100 * 1024 * 1024) + @classmethod + def _class_scope(cls, env, caddy): + cls._make_docs_file(docs_dir=caddy.docs_dir, fname='data10k.data', fsize=10 * 1024) + cls._make_docs_file(docs_dir=caddy.docs_dir, fname='data1.data', fsize=1024 * 1024) + cls._make_docs_file(docs_dir=caddy.docs_dir, fname='data5.data', fsize=5 * 1024 * 1024) + cls._make_docs_file(docs_dir=caddy.docs_dir, fname='data10.data', fsize=10 * 1024 * 1024) + cls._make_docs_file(docs_dir=caddy.docs_dir, fname='data100.data', fsize=100 * 1024 * 1024) env.make_data_file(indir=env.gen_dir, fname="data-10m", fsize=10 * 1024 * 1024) # download 1 file diff --git a/tests/http/test_09_push.py b/tests/http/test_09_push.py index 7472b3d8e39c..a77e874a38f4 100644 --- a/tests/http/test_09_push.py +++ b/tests/http/test_09_push.py @@ -34,7 +34,8 @@ class TestPush: @pytest.fixture(autouse=True, scope='class') - def _class_scope(self, env, httpd): + @classmethod + def _class_scope(cls, env, httpd): push_dir = os.path.join(httpd.docs_dir, 'push') if not os.path.exists(push_dir): os.makedirs(push_dir) diff --git a/tests/http/test_10_proxy.py b/tests/http/test_10_proxy.py index bd07841a68c0..da02cb08f3c6 100644 --- a/tests/http/test_10_proxy.py +++ b/tests/http/test_10_proxy.py @@ -37,7 +37,8 @@ class TestProxy: @pytest.fixture(autouse=True, scope='class') - def _class_scope(self, env, httpd, nghttpx_fwd): + @classmethod + def _class_scope(cls, env, httpd, nghttpx_fwd): push_dir = os.path.join(httpd.docs_dir, 'push') if not os.path.exists(push_dir): os.makedirs(push_dir) diff --git a/tests/http/test_11_unix.py b/tests/http/test_11_unix.py index 9321d7141402..436da8617c70 100644 --- a/tests/http/test_11_unix.py +++ b/tests/http/test_11_unix.py @@ -86,8 +86,9 @@ def _process(self): class TestUnix: - @pytest.fixture(scope="class") - def uds_faker(self, env: Env) -> Generator[UDSFaker, None, None]: + @pytest.fixture(scope='class') + @classmethod + def uds_faker(cls, env: Env) -> Generator[UDSFaker, None, None]: uds_path = os.path.join(env.gen_dir, 'uds_11.sock') faker = UDSFaker(path=uds_path) faker.start() diff --git a/tests/http/test_14_auth.py b/tests/http/test_14_auth.py index ab941b6efa2d..0e0e1311cd34 100644 --- a/tests/http/test_14_auth.py +++ b/tests/http/test_14_auth.py @@ -35,7 +35,8 @@ class TestAuth: @pytest.fixture(autouse=True, scope='class') - def _class_scope(self, env, httpd, nghttpx): + @classmethod + def _class_scope(cls, env, httpd, nghttpx): env.make_data_file(indir=env.gen_dir, fname="data-10m", fsize=10 * 1024 * 1024) # download 1 file, not authenticated diff --git a/tests/http/test_16_info.py b/tests/http/test_16_info.py index 0d184931e208..a7160badc7f0 100644 --- a/tests/http/test_16_info.py +++ b/tests/http/test_16_info.py @@ -34,7 +34,8 @@ class TestInfo: @pytest.fixture(autouse=True, scope='class') - def _class_scope(self, env, httpd): + @classmethod + def _class_scope(cls, env, httpd): indir = httpd.docs_dir env.make_data_file(indir=indir, fname="data-10k", fsize=10 * 1024) env.make_data_file(indir=indir, fname="data-100k", fsize=100 * 1024) diff --git a/tests/http/test_17_ssl_use.py b/tests/http/test_17_ssl_use.py index bbab6c847ed8..82310d8f13a2 100644 --- a/tests/http/test_17_ssl_use.py +++ b/tests/http/test_17_ssl_use.py @@ -64,7 +64,8 @@ class TLSDefs: class TestSSLUse: @pytest.fixture(autouse=True, scope='class') - def _class_scope(self, env, httpd, nghttpx): + @classmethod + def _class_scope(cls, env, httpd, nghttpx): env.make_data_file(indir=httpd.docs_dir, fname="data-10k", fsize=10 * 1024) def test_17_01_sslinfo_plain(self, env: Env, httpd): diff --git a/tests/http/test_18_methods.py b/tests/http/test_18_methods.py index 452b422c6b06..fc69def62bfc 100644 --- a/tests/http/test_18_methods.py +++ b/tests/http/test_18_methods.py @@ -33,7 +33,8 @@ class TestMethods: @pytest.fixture(autouse=True, scope='class') - def _class_scope(self, env, httpd, nghttpx): + @classmethod + def _class_scope(cls, env, httpd, nghttpx): indir = httpd.docs_dir env.make_data_file(indir=indir, fname="data-10k", fsize=10 * 1024) env.make_data_file(indir=indir, fname="data-100k", fsize=100 * 1024) diff --git a/tests/http/test_19_shutdown.py b/tests/http/test_19_shutdown.py index 388d3955dabe..e4fdea648bfc 100644 --- a/tests/http/test_19_shutdown.py +++ b/tests/http/test_19_shutdown.py @@ -35,7 +35,8 @@ class TestShutdown: @pytest.fixture(autouse=True, scope='class') - def _class_scope(self, env, httpd): + @classmethod + def _class_scope(cls, env, httpd): indir = httpd.docs_dir env.make_data_file(indir=indir, fname="data-10k", fsize=10 * 1024) env.make_data_file(indir=indir, fname="data-100k", fsize=100 * 1024) diff --git a/tests/http/test_20_websockets.py b/tests/http/test_20_websockets.py index c859279c0ee6..030d1015af35 100644 --- a/tests/http/test_20_websockets.py +++ b/tests/http/test_20_websockets.py @@ -111,7 +111,8 @@ def shutdown(self): class TestWebsockets: @pytest.fixture(autouse=True, scope='class') - def ws_echo(self, env): + @classmethod + def ws_echo(cls, env): cmd = os.path.join(env.project_dir, 'tests/http/testenv/ws_echo_server.py') server = WsServer('ws_echo', env, cmd) @@ -120,7 +121,8 @@ def ws_echo(self, env): server.shutdown() @pytest.fixture(autouse=True, scope='class') - def ws_4frames(self, env): + @classmethod + def ws_4frames(cls, env): cmd = os.path.join(env.project_dir, 'tests/http/testenv/ws_4frames_server.py') server = WsServer('ws_4frames', env, cmd) diff --git a/tests/http/test_21_resolve.py b/tests/http/test_21_resolve.py index 28634f14614e..d3486ea82f35 100644 --- a/tests/http/test_21_resolve.py +++ b/tests/http/test_21_resolve.py @@ -39,14 +39,16 @@ class TestResolve: @pytest.fixture(scope='class') - def dnsd(self, env: Env) -> Generator[Dnsd, None, None]: + @classmethod + def dnsd(cls, env: Env) -> Generator[Dnsd, None, None]: dnsd = Dnsd(env=env) assert dnsd.initial_start() yield dnsd dnsd.stop() @pytest.fixture(autouse=True, scope='class') - def _class_scope(self, env, httpd): + @classmethod + def _class_scope(cls, env, httpd): indir = httpd.docs_dir env.make_data_file(indir=indir, fname="data-0k", fsize=0) diff --git a/tests/http/test_22_httpsrr.py b/tests/http/test_22_httpsrr.py index 63bb2c029ed9..840bbfc48fb7 100644 --- a/tests/http/test_22_httpsrr.py +++ b/tests/http/test_22_httpsrr.py @@ -37,7 +37,8 @@ class TestHTTPSRR: @pytest.fixture(scope='class') - def dnsd(self, env: Env) -> Generator[Dnsd, None, None]: + @classmethod + def dnsd(cls, env: Env) -> Generator[Dnsd, None, None]: dnsd = Dnsd(env=env) assert dnsd.initial_start() yield dnsd diff --git a/tests/http/test_30_vsftpd.py b/tests/http/test_30_vsftpd.py index 9f76ce9a0774..756457fbca84 100644 --- a/tests/http/test_30_vsftpd.py +++ b/tests/http/test_30_vsftpd.py @@ -38,13 +38,15 @@ class TestVsFTPD: @pytest.fixture(autouse=True, scope='class') - def vsftpd(self, env): + @classmethod + def vsftpd(cls, env): vsftpd = VsFTPD(env=env) assert vsftpd.initial_start() yield vsftpd vsftpd.stop() - def _make_docs_file(self, docs_dir: str, fname: str, fsize: int): + @classmethod + def _make_docs_file(cls, docs_dir: str, fname: str, fsize: int): fpath = os.path.join(docs_dir, fname) data1k = 1024*'x' flen = 0 @@ -57,16 +59,17 @@ def _make_docs_file(self, docs_dir: str, fname: str, fsize: int): return flen @pytest.fixture(autouse=True, scope='class') - def _class_scope(self, env, vsftpd): + @classmethod + def _class_scope(cls, env, vsftpd): if os.path.exists(vsftpd.docs_dir): shutil.rmtree(vsftpd.docs_dir) if not os.path.exists(vsftpd.docs_dir): os.makedirs(vsftpd.docs_dir) - self._make_docs_file(docs_dir=vsftpd.docs_dir, fname='data-0k', fsize=0) - self._make_docs_file(docs_dir=vsftpd.docs_dir, fname='data-1k', fsize=1024) - self._make_docs_file(docs_dir=vsftpd.docs_dir, fname='data-10k', fsize=10 * 1024) - self._make_docs_file(docs_dir=vsftpd.docs_dir, fname='data-1m', fsize=1024 * 1024) - self._make_docs_file(docs_dir=vsftpd.docs_dir, fname='data-10m', fsize=10 * 1024 * 1024) + cls._make_docs_file(docs_dir=vsftpd.docs_dir, fname='data-0k', fsize=0) + cls._make_docs_file(docs_dir=vsftpd.docs_dir, fname='data-1k', fsize=1024) + cls._make_docs_file(docs_dir=vsftpd.docs_dir, fname='data-10k', fsize=10 * 1024) + cls._make_docs_file(docs_dir=vsftpd.docs_dir, fname='data-1m', fsize=1024 * 1024) + cls._make_docs_file(docs_dir=vsftpd.docs_dir, fname='data-10m', fsize=10 * 1024 * 1024) env.make_data_file(indir=env.gen_dir, fname="upload-0k", fsize=0) env.make_data_file(indir=env.gen_dir, fname="upload-1k", fsize=1024) env.make_data_file(indir=env.gen_dir, fname="upload-100k", fsize=100 * 1024) diff --git a/tests/http/test_31_vsftpds.py b/tests/http/test_31_vsftpds.py index debb8c9b5bc1..113e164e1938 100644 --- a/tests/http/test_31_vsftpds.py +++ b/tests/http/test_31_vsftpds.py @@ -44,7 +44,8 @@ class TestVsFTPD: SUPPORTS_SSL = True @pytest.fixture(autouse=True, scope='class') - def vsftpds(self, env): + @classmethod + def vsftpds(cls, env): if not TestVsFTPD.SUPPORTS_SSL: pytest.skip('vsftpd does not seem to support SSL') vsftpds = VsFTPD(env=env, with_ssl=True) @@ -55,7 +56,8 @@ def vsftpds(self, env): yield vsftpds vsftpds.stop() - def _make_docs_file(self, docs_dir: str, fname: str, fsize: int): + @classmethod + def _make_docs_file(cls, docs_dir: str, fname: str, fsize: int): fpath = os.path.join(docs_dir, fname) data1k = 1024*'x' flen = 0 @@ -66,15 +68,16 @@ def _make_docs_file(self, docs_dir: str, fname: str, fsize: int): return flen @pytest.fixture(autouse=True, scope='class') - def _class_scope(self, env, vsftpds): + @classmethod + def _class_scope(cls, env, vsftpds): if os.path.exists(vsftpds.docs_dir): shutil.rmtree(vsftpds.docs_dir) if not os.path.exists(vsftpds.docs_dir): os.makedirs(vsftpds.docs_dir) - self._make_docs_file(docs_dir=vsftpds.docs_dir, fname='data-1k', fsize=1024) - self._make_docs_file(docs_dir=vsftpds.docs_dir, fname='data-10k', fsize=10 * 1024) - self._make_docs_file(docs_dir=vsftpds.docs_dir, fname='data-1m', fsize=1024 * 1024) - self._make_docs_file(docs_dir=vsftpds.docs_dir, fname='data-10m', fsize=10 * 1024 * 1024) + cls._make_docs_file(docs_dir=vsftpds.docs_dir, fname='data-1k', fsize=1024) + cls._make_docs_file(docs_dir=vsftpds.docs_dir, fname='data-10k', fsize=10 * 1024) + cls._make_docs_file(docs_dir=vsftpds.docs_dir, fname='data-1m', fsize=1024 * 1024) + cls._make_docs_file(docs_dir=vsftpds.docs_dir, fname='data-10m', fsize=10 * 1024 * 1024) env.make_data_file(indir=env.gen_dir, fname="upload-1k", fsize=1024) env.make_data_file(indir=env.gen_dir, fname="upload-100k", fsize=100 * 1024) env.make_data_file(indir=env.gen_dir, fname="upload-1m", fsize=1024 * 1024) diff --git a/tests/http/test_32_ftps_vsftpd.py b/tests/http/test_32_ftps_vsftpd.py index 6db7fb1eaa0f..1328a0b34b1b 100644 --- a/tests/http/test_32_ftps_vsftpd.py +++ b/tests/http/test_32_ftps_vsftpd.py @@ -44,7 +44,8 @@ class TestFtpsVsFTPD: SUPPORTS_SSL = True @pytest.fixture(autouse=True, scope='class') - def vsftpds(self, env): + @classmethod + def vsftpds(cls, env): if not TestFtpsVsFTPD.SUPPORTS_SSL: pytest.skip('vsftpd does not seem to support SSL') vsftpds = VsFTPD(env=env, with_ssl=True, ssl_implicit=True) @@ -55,7 +56,8 @@ def vsftpds(self, env): yield vsftpds vsftpds.stop() - def _make_docs_file(self, docs_dir: str, fname: str, fsize: int): + @classmethod + def _make_docs_file(cls, docs_dir: str, fname: str, fsize: int): fpath = os.path.join(docs_dir, fname) data1k = 1024*'x' flen = 0 @@ -66,15 +68,16 @@ def _make_docs_file(self, docs_dir: str, fname: str, fsize: int): return flen @pytest.fixture(autouse=True, scope='class') - def _class_scope(self, env, vsftpds): + @classmethod + def _class_scope(cls, env, vsftpds): if os.path.exists(vsftpds.docs_dir): shutil.rmtree(vsftpds.docs_dir) if not os.path.exists(vsftpds.docs_dir): os.makedirs(vsftpds.docs_dir) - self._make_docs_file(docs_dir=vsftpds.docs_dir, fname='data-1k', fsize=1024) - self._make_docs_file(docs_dir=vsftpds.docs_dir, fname='data-10k', fsize=10 * 1024) - self._make_docs_file(docs_dir=vsftpds.docs_dir, fname='data-1m', fsize=1024 * 1024) - self._make_docs_file(docs_dir=vsftpds.docs_dir, fname='data-10m', fsize=10 * 1024 * 1024) + cls._make_docs_file(docs_dir=vsftpds.docs_dir, fname='data-1k', fsize=1024) + cls._make_docs_file(docs_dir=vsftpds.docs_dir, fname='data-10k', fsize=10 * 1024) + cls._make_docs_file(docs_dir=vsftpds.docs_dir, fname='data-1m', fsize=1024 * 1024) + cls._make_docs_file(docs_dir=vsftpds.docs_dir, fname='data-10m', fsize=10 * 1024 * 1024) env.make_data_file(indir=env.gen_dir, fname="upload-1k", fsize=1024) env.make_data_file(indir=env.gen_dir, fname="upload-100k", fsize=100 * 1024) env.make_data_file(indir=env.gen_dir, fname="upload-1m", fsize=1024 * 1024) diff --git a/tests/http/test_40_socks.py b/tests/http/test_40_socks.py index 186b75e43072..e29d574c1ae5 100644 --- a/tests/http/test_40_socks.py +++ b/tests/http/test_40_socks.py @@ -37,7 +37,8 @@ class TestSocks: @pytest.fixture(scope='class') - def danted(self, env: Env) -> Generator[Dante, None, None]: + @classmethod + def danted(cls, env: Env) -> Generator[Dante, None, None]: danted = Dante(env=env) assert danted.initial_start() time.sleep(1) @@ -45,7 +46,8 @@ def danted(self, env: Env) -> Generator[Dante, None, None]: danted.stop() @pytest.fixture(autouse=True, scope='class') - def _class_scope(self, env, httpd): + @classmethod + def _class_scope(cls, env, httpd): indir = httpd.docs_dir env.make_data_file(indir=indir, fname="data-10m", fsize=10 * 1024 * 1024) env.make_data_file(indir=env.gen_dir, fname="data-10m", fsize=10 * 1024 * 1024) diff --git a/tests/http/test_50_scp.py b/tests/http/test_50_scp.py index 13c36acd6571..14be668d7599 100644 --- a/tests/http/test_50_scp.py +++ b/tests/http/test_50_scp.py @@ -38,7 +38,8 @@ class TestScp: @pytest.fixture(autouse=True, scope='class') - def _class_scope(self, env, sshd): + @classmethod + def _class_scope(cls, env, sshd): env.make_data_file(indir=sshd.home_dir, fname="data-10k", fsize=10 * 1024) env.make_data_file(indir=sshd.home_dir, fname="data-10m", fsize=10 * 1024 * 1024) env.make_data_file(indir=env.gen_dir, fname="data-10k", fsize=10 * 1024) diff --git a/tests/http/test_51_sftp.py b/tests/http/test_51_sftp.py index 6caaad9d25ba..4150e3972ca0 100644 --- a/tests/http/test_51_sftp.py +++ b/tests/http/test_51_sftp.py @@ -38,7 +38,8 @@ class TestSftp: @pytest.fixture(autouse=True, scope='class') - def _class_scope(self, env, sshd): + @classmethod + def _class_scope(cls, env, sshd): env.make_data_file(indir=sshd.home_dir, fname="data-10k", fsize=10 * 1024) env.make_data_file(indir=sshd.home_dir, fname="data-10m", fsize=10 * 1024 * 1024) env.make_data_file(indir=env.gen_dir, fname="data-10k", fsize=10 * 1024) From 70a5eed6838669af2fa467d00b5dd7dad27eab64 Mon Sep 17 00:00:00 2001 From: Viktor Szakats Date: Mon, 28 Sep 2026 15:08:52 +0200 Subject: [PATCH 287/481] curl_setup.h: drop compatibility raw C flag `ENABLE_IPV6` curl's supported build systems do not use it. If you use it as a manual C flag, replace it with `USE_IPV6`. Follow-up to e411c98f702f0fb38dceec95e7507ef15a00d12c #13349 Closes #23138 --- lib/curl_setup.h | 5 ----- 1 file changed, 5 deletions(-) diff --git a/lib/curl_setup.h b/lib/curl_setup.h index 50bbbe46b6bb..758948286d2b 100644 --- a/lib/curl_setup.h +++ b/lib/curl_setup.h @@ -125,11 +125,6 @@ #define SECURITY_WIN32 /* for */ #endif -/* Compatibility */ -#ifdef ENABLE_IPV6 -#define USE_IPV6 1 -#endif - /* * Include configuration script results or hand-crafted * configuration file for platforms which lack config tool. From 013c14abf5612f8fb04c2acba8c7572e46772261 Mon Sep 17 00:00:00 2001 From: Viktor Szakats Date: Mon, 28 Sep 2026 15:15:32 +0200 Subject: [PATCH 288/481] cmake/Find*: drop deprecated compatibility config variables If you use these variables, replace them like so: - `MBEDTLS_INCLUDE_DIRS` -> `MBEDTLS_INCLUDE_DIR` - `WolfSSL_INCLUDE_DIR` -> `WOLFSSL_INCLUDE_DIR` - `WolfSSL_LIBRARY` -> `WOLFSSL_LIBRARY` - `Zstd_INCLUDE_DIR` -> `ZSTD_INCLUDE_DIR` - `Zstd_LIBRARY` -> `ZSTD_LIBRARY` They were deprecated two years ago in curl 8.10.0. Follow-up to 9fbda4ca75483ee0a43289526e88d8f1e8ca2a78 #14574 Follow-up to db39c668a8e33e064b9eb20892cd027f46302f77 #14542 Closes #23139 --- CMake/FindMbedTLS.cmake | 6 ------ CMake/FindWolfSSL.cmake | 9 --------- CMake/FindZstd.cmake | 9 --------- 3 files changed, 24 deletions(-) diff --git a/CMake/FindMbedTLS.cmake b/CMake/FindMbedTLS.cmake index 21a5f4aec734..804596a261e1 100644 --- a/CMake/FindMbedTLS.cmake +++ b/CMake/FindMbedTLS.cmake @@ -37,12 +37,6 @@ # - `MBEDTLS_VERSION`: Version of mbedTLS. # - `CURL::mbedtls`: mbedTLS library target. -if(DEFINED MBEDTLS_INCLUDE_DIRS AND NOT DEFINED MBEDTLS_INCLUDE_DIR) - message(WARNING "MBEDTLS_INCLUDE_DIRS is deprecated, use MBEDTLS_INCLUDE_DIR instead.") - set(MBEDTLS_INCLUDE_DIR "${MBEDTLS_INCLUDE_DIRS}") - unset(MBEDTLS_INCLUDE_DIRS) -endif() - set(_mbedtls_pc_requires "mbedtls" "mbedx509" "mbedcrypto") if(NOT DEFINED MBEDTLS_INCLUDE_DIR AND diff --git a/CMake/FindWolfSSL.cmake b/CMake/FindWolfSSL.cmake index 964320ad6b60..32a9656f3120 100644 --- a/CMake/FindWolfSSL.cmake +++ b/CMake/FindWolfSSL.cmake @@ -34,15 +34,6 @@ # - `WOLFSSL_VERSION`: Version of wolfSSL. # - `CURL::wolfssl`: wolfSSL library target. -if(DEFINED WolfSSL_INCLUDE_DIR AND NOT DEFINED WOLFSSL_INCLUDE_DIR) - message(WARNING "WolfSSL_INCLUDE_DIR is deprecated, use WOLFSSL_INCLUDE_DIR instead.") - set(WOLFSSL_INCLUDE_DIR "${WolfSSL_INCLUDE_DIR}") -endif() -if(DEFINED WolfSSL_LIBRARY AND NOT DEFINED WOLFSSL_LIBRARY) - message(WARNING "WolfSSL_LIBRARY is deprecated, use WOLFSSL_LIBRARY instead.") - set(WOLFSSL_LIBRARY "${WolfSSL_LIBRARY}") -endif() - set(_wolfssl_pc_requires "wolfssl") if(NOT DEFINED WOLFSSL_INCLUDE_DIR AND diff --git a/CMake/FindZstd.cmake b/CMake/FindZstd.cmake index 5a2db76bb1a2..81ecd746d94b 100644 --- a/CMake/FindZstd.cmake +++ b/CMake/FindZstd.cmake @@ -35,15 +35,6 @@ # - `ZSTD_VERSION`: Version of zstd. # - `CURL::zstd`: zstd library target. -if(DEFINED Zstd_INCLUDE_DIR AND NOT DEFINED ZSTD_INCLUDE_DIR) - message(WARNING "Zstd_INCLUDE_DIR is deprecated, use ZSTD_INCLUDE_DIR instead.") - set(ZSTD_INCLUDE_DIR "${Zstd_INCLUDE_DIR}") -endif() -if(DEFINED Zstd_LIBRARY AND NOT DEFINED ZSTD_LIBRARY) - message(WARNING "Zstd_LIBRARY is deprecated, use ZSTD_LIBRARY instead.") - set(ZSTD_LIBRARY "${Zstd_LIBRARY}") -endif() - set(_zstd_pc_requires "libzstd") if(NOT DEFINED ZSTD_INCLUDE_DIR AND From f197dfd250505d6dd7454779d6cf4cbb4fd4487d Mon Sep 17 00:00:00 2001 From: Viktor Szakats Date: Mon, 28 Sep 2026 16:14:00 +0200 Subject: [PATCH 289/481] GHA/windows: install Python for Cygwin tests To avoid "python missing" errors and running DICT and telnet tests instead. It needs to install 2 extra packages: ``` 128 install python312 3.12.12-1 136 install python3 3.12.12-1 ``` And enables 3 runtest tests: 1450, 1452, 3555 Fixing: ``` /usr/bin/env: 'python3': No such file or directory /usr/bin/env: 'python3': No such file or directory ``` Ref: https://github.com/curl/curl/actions/runs/36429133927/job/108950548254#step:13:2893 Ideally, `dictserver.py` and `negtelnetserver.py` would be (re)written in Perl to avoid this oddball dependency exception. Also: - tried installing `python` (without `3`) first, which resulted in a 3rd extra package `python 3.0.0-1`. Settled on `python3`. - bump minimum number of tests. Cherry-picked from #23137 Closes #23141 --- .github/workflows/windows.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index d81704c0a4cb..30a21180e685 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -81,7 +81,7 @@ jobs: run: shell: D:\cygwin\bin\bash.exe '{0}' # zizmor: ignore[misfeature] env: - CURL_TEST_MIN: 1850 + CURL_TEST_MIN: 2000 LDFLAGS: -s MAKEFLAGS: -j 5 SHELLOPTS: 'igncr' @@ -96,7 +96,7 @@ jobs: - { name: 'openssl', build: 'cmake', platform: 'x86_64', tflags: '', config: '-DENABLE_DEBUG=ON -DCURL_USE_OPENSSL=ON -DENABLE_THREADED_RESOLVER=OFF -DCURL_ENABLE_NTLM=ON', - install: 'libssl-devel libssh2-devel' } + install: 'libssl-devel libssh2-devel python3' } fail-fast: false steps: - uses: cygwin/cygwin-install-action@3f0a3f9f988f7e96b8c18098ae05eaec175f5b52 # v6.0.2 From 6ec29b55ee62a7a654ea7bdb6c76c3773f329b11 Mon Sep 17 00:00:00 2001 From: Viktor Szakats Date: Mon, 28 Sep 2026 15:51:52 +0200 Subject: [PATCH 290/481] ftpserver.pl: fix warnings when running test 804 A recent patch introduced a test using an irregular IMAP folder, which isn't the test number, but a string, causing a Perl warning. Fixing warnings in runtests log when running test 804: ``` Argument "" isn't numeric in numeric gt (>) at /home/runner/work/curl/curl/tests/ftpserver.pl line 445. Argument "" isn't numeric in numeric gt (>) at /home/runner/work/curl/curl/tests/ftpserver.pl line 445. ``` Ref: https://github.com/curl/curl/actions/runs/36429133995/job/108950479015#step:50:1589 Follow-up to 12cfc836c6fe8f4e4e5001f400d677f0aa530421 #22868 Follow-up to 3a29ee41ef72c3a50d889e744ffdce6419234388 Closes #23140 --- tests/ftpserver.pl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/ftpserver.pl b/tests/ftpserver.pl index 80bb606be1c9..98c49f1cdea6 100755 --- a/tests/ftpserver.pl +++ b/tests/ftpserver.pl @@ -442,7 +442,7 @@ sub getreplydata { my $testpart = ""; $num =~ s/^([^0-9]*)//; - if($num > 10000) { + if($num && $num > 10000) { $testpart = $num % 10000; } From 852ebe5dd1e2bc634e616afc2ecf5a00269d83b4 Mon Sep 17 00:00:00 2001 From: Viktor Szakats Date: Mon, 28 Sep 2026 16:37:44 +0200 Subject: [PATCH 291/481] test2072: enable on Cygwin/MSYS2 Replace custom Perl precheck logic with `!win32`, to let it run on Cygwin/MSYS2, which behave as Unix. Also drop the redundant MS-DOS filter. Fixing in 'Cygwin, CM' and 'msys2, CM x86_64 default' jobs: ``` TESTINFO: "Test requires a Unix system" 1 time (2072) ``` Ref: https://github.com/curl/curl/actions/runs/36438133012/job/108981342315#step:13:4430 Ref: https://github.com/curl/curl/actions/runs/36438133012/job/108981342518#step:16:4469 Follow-up to 695e96b3d5b9e6ae4832af5f8e99e07de960bf33 #2438 Closes #23142 --- tests/data/test2072 | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/data/test2072 b/tests/data/test2072 index efad82012a8b..9b1599ed0f81 100644 --- a/tests/data/test2072 +++ b/tests/data/test2072 @@ -21,15 +21,15 @@ moo file + +!win32 + file:// with Unix path for the case of extra slashes file:////%FILE_PWD/%LOGDIR/test%TESTNUMBER.txt - -%PERL -e "print 'Test requires a Unix system' if($^O eq 'MSWin32' || $^O eq 'cygwin' || $^O eq 'dos' || $^O eq 'msys');" - foo bar From 4a2bed0cbc4be84aca14590b153f02ca68e7ee4b Mon Sep 17 00:00:00 2001 From: Viktor Szakats Date: Mon, 28 Sep 2026 17:03:58 +0200 Subject: [PATCH 292/481] GHA/windows: enable httpsig in Cygwin/MSYS2 jobs Also bump required minimum number of tests. Follow-up to a55731050e8c3dbea0b96205cf916443118f6acb #22386 #21239 Closes #23143 --- .github/workflows/windows.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 30a21180e685..e1271718f864 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -81,7 +81,7 @@ jobs: run: shell: D:\cygwin\bin\bash.exe '{0}' # zizmor: ignore[misfeature] env: - CURL_TEST_MIN: 2000 + CURL_TEST_MIN: 2040 LDFLAGS: -s MAKEFLAGS: -j 5 SHELLOPTS: 'igncr' @@ -95,7 +95,7 @@ jobs: install: 'libssl-devel libssh2-devel' } - { name: 'openssl', build: 'cmake', platform: 'x86_64', tflags: '', - config: '-DENABLE_DEBUG=ON -DCURL_USE_OPENSSL=ON -DENABLE_THREADED_RESOLVER=OFF -DCURL_ENABLE_NTLM=ON', + config: '-DENABLE_DEBUG=ON -DCURL_USE_OPENSSL=ON -DENABLE_THREADED_RESOLVER=OFF -DCURL_DISABLE_HTTPSIG=OFF -DCURL_ENABLE_NTLM=ON', install: 'libssl-devel libssh2-devel python3' } fail-fast: false steps: @@ -262,8 +262,8 @@ jobs: include: # MSYS - { name: '!proxy', - build: 'autotools', sys: 'msys' , env: 'x86_64' , tflags: '--min=1620', - config: '--enable-debug --with-openssl --disable-threaded-resolver --disable-proxy --enable-ntlm', + build: 'autotools', sys: 'msys' , env: 'x86_64' , tflags: '--min=1750', + config: '--enable-debug --with-openssl --disable-threaded-resolver --disable-proxy --enable-httpsig --enable-ntlm', install: 'openssl-devel libssh2-devel' } - { name: 'default', build: 'autotools', sys: 'msys' , env: 'x86_64' , tflags: 'skiprun', From 82636cdfacccd3fb786559c176a7bf182ba018f9 Mon Sep 17 00:00:00 2001 From: Viktor Szakats Date: Mon, 28 Sep 2026 14:00:31 +0200 Subject: [PATCH 293/481] GHA/windows: install Python for a MSYS2/mingw job To enable 3 runtests (DICT, telnet). Keep it limited because the Python dependency is large. (MSYS2: 2 packages, 180MB installed. mingw: 15 packages, 280MB installed.) dl-mingw and MSVC jobs find the system Python due to using MSYS2 with `path-type: inherit`. Enabling it for MSYS2 jobs confuses autotools, so instead of doing this, install python manually. Make sure to use the mingw build to avoid this silent fallout: ``` python3: can't open file '/d/a/curl/curl/bld/tests/D:/a/curl/curl/tests/dictserver.py': [Errno 2] No such file or directory python3: can't open file '/d/a/curl/curl/bld/tests/D:/a/curl/curl/tests/negtelnetserver.py': [Errno 2] No such file or directory ``` Ref: https://github.com/curl/curl/actions/runs/36444312522/job/109002584663?pr=23137#step:16:2703 Fixing: ``` /usr/bin/env: 'python3': No such file or directory /usr/bin/env: 'python3': No such file or directory ``` Ref: https://github.com/curl/curl/actions/runs/36411674440/job/108893140670#step:16:2706 Also: - GHA/windows: bump required number of tests. Follow-up to f197dfd250505d6dd7454779d6cf4cbb4fd4487d #23141 Closes #23137 --- .github/workflows/windows.yml | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index e1271718f864..d5e7ab0297b7 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -36,7 +36,7 @@ cache-mode: write env: CURL_CI: github - CURL_TEST_MIN: 1800 + CURL_TEST_MIN: 1900 CURL_TEST_SSH_KEYALGO: ed25519 DO_NOT_TRACK: '1' OPENSSH_WINDOWS_VERSION: 10.0.0.0p2-Preview @@ -275,7 +275,8 @@ jobs: install: 'openssl-devel libssh2-devel' } - { name: 'default R', build: 'autotools', sys: 'msys' , env: 'x86_64' , tflags: '', - config: '--with-openssl --enable-ntlm', install: 'openssl-devel libssh2-devel' } + config: '--with-openssl --enable-ntlm', + install: 'openssl-devel libssh2-devel' } # MinGW - { name: 'default', build: 'autotools', sys: 'clang64' , env: 'clang-x86_64' , tflags: 'skiprun', @@ -286,16 +287,16 @@ jobs: config: '--enable-debug --with-wolfssl --enable-windows-unicode --enable-ares --enable-static --disable-shared --enable-ca-native --enable-ntlm', install: 'mingw-w64-ucrt-x86_64-c-ares mingw-w64-ucrt-x86_64-nghttp3 mingw-w64-ucrt-x86_64-libssh2 mingw-w64-ucrt-x86_64-wolfssl' } - { name: 'schannel c-ares U', type: 'Debug', - build: 'cmake' , sys: 'clang64' , env: 'clang-x86_64' , tflags: '--min=1720', + build: 'cmake' , sys: 'clang64' , env: 'clang-x86_64' , tflags: '--min=1850', config: '-DENABLE_DEBUG=ON -DBUILD_SHARED_LIBS=OFF -DCURL_USE_SCHANNEL=ON -DENABLE_UNICODE=ON -DENABLE_ARES=ON -DCURL_DROP_UNUSED=ON', - install: 'mingw-w64-clang-x86_64-c-ares mingw-w64-clang-x86_64-libssh2' } + install: 'mingw-w64-clang-x86_64-c-ares mingw-w64-clang-x86_64-libssh2 mingw-w64-clang-x86_64-python' } # MinGW torture - { name: 'schannel U torture 1', type: 'Debug', - build: 'cmake' , sys: 'ucrt64' , env: 'ucrt-x86_64' , tflags: '-t --shallow=13 --subset=0/2', + build: 'cmake' , sys: 'ucrt64' , env: 'ucrt-x86_64' , tflags: '-t --shallow=13 --subset=0/2 --min=920', config: '-DENABLE_DEBUG=ON -DBUILD_SHARED_LIBS=OFF -DCURL_USE_SCHANNEL=ON -DENABLE_UNICODE=ON -DENABLE_ARES=ON', install: 'mingw-w64-ucrt-x86_64-c-ares mingw-w64-ucrt-x86_64-libssh2' } - { name: 'schannel U torture 2', type: 'Debug', - build: 'cmake' , sys: 'ucrt64' , env: 'ucrt-x86_64' , tflags: '-t --shallow=13 --subset=1/2 --min=885', + build: 'cmake' , sys: 'ucrt64' , env: 'ucrt-x86_64' , tflags: '-t --shallow=13 --subset=1/2 --min=900', config: '-DENABLE_DEBUG=ON -DBUILD_SHARED_LIBS=OFF -DCURL_USE_SCHANNEL=ON -DENABLE_UNICODE=ON -DENABLE_ARES=ON', install: 'mingw-w64-ucrt-x86_64-c-ares mingw-w64-ucrt-x86_64-libssh2' } # WARNING: libssh uses hard-coded world-writable paths (C:ProgramData/, /etc/..., ~/.ssh/) @@ -591,7 +592,7 @@ jobs: run: shell: msys2 {0} # zizmor: ignore[misfeature] env: - CURL_TEST_MIN: 1630 + CURL_TEST_MIN: 1780 LDFLAGS: -s MAKEFLAGS: -j 5 MATRIX_DIR: '${{ matrix.dir }}' @@ -958,7 +959,7 @@ jobs: type: 'Debug' image: 'windows-2025' chkprefill: '_chkprefill' - tflags: '--min=1850' + tflags: '--min=1980' config: >- -DENABLE_DEBUG=ON -DCURL_USE_OPENSSL=ON -DUSE_NGTCP2=ON @@ -995,7 +996,7 @@ jobs: type: 'Debug' image: 'windows-11-arm' openssh: 'OpenSSH-Windows' - tflags: '--min=1720' + tflags: '--min=1850' config: >- -DENABLE_DEBUG=ON -DCURL_USE_SCHANNEL=ON From 37a6e61f736973c51aa76241b7bc0c195c6420d7 Mon Sep 17 00:00:00 2001 From: Viktor Szakats Date: Mon, 28 Sep 2026 20:40:54 +0200 Subject: [PATCH 294/481] GHA: bump required test minimums Closes #23145 --- .github/workflows/distcheck.yml | 2 +- .github/workflows/http3-linux.yml | 6 +++--- .github/workflows/linux-old.yml | 2 +- .github/workflows/linux.yml | 16 ++++++++-------- .github/workflows/macos.yml | 12 ++++++------ .github/workflows/non-native.yml | 2 +- 6 files changed, 20 insertions(+), 20 deletions(-) diff --git a/.github/workflows/distcheck.yml b/.github/workflows/distcheck.yml index 178c9b642e61..49193488a677 100644 --- a/.github/workflows/distcheck.yml +++ b/.github/workflows/distcheck.yml @@ -23,7 +23,7 @@ permissions: {} cache-mode: none env: - CURL_TEST_MIN: 1500 + CURL_TEST_MIN: 1600 DO_NOT_TRACK: '1' MAKEFLAGS: -j 5 diff --git a/.github/workflows/http3-linux.yml b/.github/workflows/http3-linux.yml index 00d5c2fc5daa..2a8f6e59b1cf 100644 --- a/.github/workflows/http3-linux.yml +++ b/.github/workflows/http3-linux.yml @@ -37,7 +37,7 @@ cache-mode: write env: MAKEFLAGS: -j 5 CURL_CI: github - CURL_TEST_MIN: 1850 + CURL_TEST_MIN: 2000 DO_NOT_TRACK: '1' # renovate: datasource=github-tags depName=awslabs/aws-lc versioning=semver registryUrl=https://github.com AWSLC_VERSION: 5.8.0 @@ -502,7 +502,7 @@ jobs: -DOPENSSL_ROOT_DIR=/home/runner/libressl/build -DUSE_NGTCP2=ON -DCURL_DISABLE_HTTPSIG=OFF - name: 'openssl' - tflags: '--min=1700' + tflags: '--min=1850' LDFLAGS: -Wl,-rpath,/home/runner/openssl/build/lib PKG_CONFIG_PATH: /home/runner/openssl/build/lib/pkgconfig:/home/runner/nghttp3/build/lib/pkgconfig:/home/runner/nghttp2/build/lib/pkgconfig configure: >- @@ -528,7 +528,7 @@ jobs: --with-openssl=/home/runner/openssl-prev/build --with-ngtcp2=/home/runner/ngtcp2-openssl-prev/build --enable-ssls-export - name: 'openssl-prev' - tflags: '--min=1700' + tflags: '--min=2000' PKG_CONFIG_PATH: "\ /home/runner/openssl-prev/build/lib/pkgconfig:\ /home/runner/nghttp3/build/lib/pkgconfig:\ diff --git a/.github/workflows/linux-old.yml b/.github/workflows/linux-old.yml index 510534caa0c1..6279ca2d71ca 100644 --- a/.github/workflows/linux-old.yml +++ b/.github/workflows/linux-old.yml @@ -51,7 +51,7 @@ cache-mode: none env: MAKEFLAGS: -j 5 CURL_CI: github - CURL_TEST_MIN: 1560 + CURL_TEST_MIN: 1680 DEBIAN_FRONTEND: noninteractive DO_NOT_TRACK: '1' diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index d7059a93e2bc..5f1f77c61e53 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -35,7 +35,7 @@ cache-mode: write env: MAKEFLAGS: -j 5 CURL_CI: github - CURL_TEST_MIN: 1660 + CURL_TEST_MIN: 1800 DO_NOT_TRACK: '1' # renovate: datasource=github-tags depName=awslabs/aws-lc versioning=semver registryUrl=https://github.com AWSLC_VERSION: 5.8.0 @@ -179,7 +179,7 @@ jobs: install_packages: libnghttp2-dev libldap-dev valgrind install_steps: rust rustls-arm PKG_CONFIG_PATH: /home/runner/rustls/lib/pkgconfig - tflags: '--subset=0/2' + tflags: '--subset=0/2 --min=880' generate: -DCURL_USE_RUSTLS=ON -DUSE_ECH=ON -DENABLE_DEBUG=ON - name: 'rustls valgrind 2' @@ -207,7 +207,7 @@ jobs: image: ubuntu-26.04-arm install_packages: valgrind install_steps: wolfssl-opensslextra-arm - tflags: '--subset=0/2' + tflags: '--subset=0/2 --min=880' LDFLAGS: -Wl,-rpath,/home/runner/wolfssl-opensslextra/lib configure: --with-wolfssl=/home/runner/wolfssl-opensslextra --enable-ech --enable-debug --enable-httpsig @@ -287,7 +287,7 @@ jobs: - name: 'curl_global_init_mem debug valgrind' image: ubuntu-24.04-arm install_packages: valgrind - tflags: '--min=1700' + tflags: '--min=1800' configure: >- --enable-init-mem-debug --with-openssl --disable-debug --enable-unity @@ -295,7 +295,7 @@ jobs: - name: 'openssl https-only' image: ubuntu-24.04-arm - tflags: '--min=1260' + tflags: '--min=1360' configure: >- --with-openssl --enable-debug --disable-unity --disable-dict --disable-gopher --disable-ldap --disable-telnet @@ -330,7 +330,7 @@ jobs: - name: '!ssl !http !smtp !imap' image: ubuntu-26.04-arm - tflags: '--min=500' + tflags: '--min=520' configure: --without-ssl --enable-debug --disable-http --disable-smtp --disable-imap --disable-unity --enable-httpsig - name: 'clang-tidy' @@ -441,13 +441,13 @@ jobs: LDFLAGS: -fsanitize=memory LIBS: -ldl configure: --without-ssl --without-zlib --without-brotli --without-zstd --without-libpsl --without-nghttp2 --enable-debug - tflags: '--min=1540' + tflags: '--min=1640' - name: 'event-based' image: ubuntu-24.04 install_packages: libssh-dev configure: --enable-debug --enable-static --disable-shared --disable-threaded-resolver --with-libssh --with-openssl --enable-ntlm - tflags: '-n --test-event --min=1420' + tflags: '-n --test-event --min=1520' - name: 'duphandle' image: ubuntu-26.04-arm diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index d043ad20be8a..d76fac8a4506 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -48,7 +48,7 @@ cache-mode: none env: CURL_CI: github - CURL_TEST_MIN: 1750 + CURL_TEST_MIN: 1850 DO_NOT_TRACK: '1' MAKEFLAGS: -j 4 LDFLAGS: -w # suppress 'object file was built for newer macOS version than being linked' warnings @@ -234,13 +234,13 @@ jobs: - name: '!ssl !debug brotli zstd' compiler: gcc-13 configure: --without-ssl --with-brotli --with-zstd --with-apple-idn - tflags: '--min=1520' + tflags: '--min=1620' xcode: '' # default Xcode. Set it once to silence actionlint. - name: '!ssl libssh2 AppleIDN' compiler: clang generate: -DENABLE_DEBUG=ON -DCURL_USE_LIBSSH2=ON -DUSE_APPLE_IDN=ON -DCURL_ENABLE_SSL=OFF -DCURL_BROTLI=OFF -DCURL_ZSTD=OFF -DCURL_DISABLE_HTTPSIG=OFF - tflags: '--min=1630' + tflags: '--min=1750' - name: 'OpenSSL libssh c-ares' compiler: clang @@ -255,7 +255,7 @@ jobs: - name: '!ssl HTTP-only c-ares' macos-version-min: '10.15' # Catalina (2019) compiler: clang - tflags: '--min=960' + tflags: '--min=1040' generate: >- -DENABLE_DEBUG=ON -DENABLE_ARES=ON -DCURL_ENABLE_SSL=OFF -DCURL_DISABLE_ALTSVC=ON -DENABLE_UNIX_SOCKETS=OFF @@ -297,7 +297,7 @@ jobs: - name: 'OpenSSL event-based' compiler: clang generate: -DENABLE_DEBUG=ON -DOPENSSL_ROOT_DIR=/opt/homebrew/opt/openssl -DCURL_BROTLI=OFF -DCURL_ZSTD=OFF -DCURL_USE_LIBSSH2=OFF -DCURL_ENABLE_NTLM=ON - tflags: '--test-event --min=1400' + tflags: '--test-event --min=1450' - name: 'OpenSSL gsasl AppleIDN SecTrust +examples' compiler: clang @@ -381,7 +381,7 @@ jobs: compiler: clang install: rustls-ffi generate: -DENABLE_DEBUG=ON -DCURL_USE_RUSTLS=ON -DUSE_ECH=ON -DCURL_DISABLE_LDAP=ON -DCURL_ENABLE_NTLM=ON - tflags: '--min=1730' + tflags: '--min=1850' - name: 'OpenSSL torture 1' compiler: clang diff --git a/.github/workflows/non-native.yml b/.github/workflows/non-native.yml index 2b5f0b0b3cdc..3e7bf1abd347 100644 --- a/.github/workflows/non-native.yml +++ b/.github/workflows/non-native.yml @@ -36,7 +36,7 @@ cache-mode: none env: CURL_CI: github - CURL_TEST_MIN: 1820 + CURL_TEST_MIN: 1940 DO_NOT_TRACK: '1' jobs: From 87c5b26a0ad5569750b109941acd919f4cfb8e95 Mon Sep 17 00:00:00 2001 From: Viktor Szakats Date: Mon, 28 Sep 2026 22:32:59 +0200 Subject: [PATCH 295/481] DEPRECATE.md: reflect SMB removal Follow-up to 7bbaf2fffd947a1b7d1b96993caf00b9452200bd #22652 Closes #23147 --- docs/DEPRECATE.md | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/docs/DEPRECATE.md b/docs/DEPRECATE.md index e20fba5d617d..27e1052e8056 100644 --- a/docs/DEPRECATE.md +++ b/docs/DEPRECATE.md @@ -12,12 +12,6 @@ email the as soon as possible and explain to us why this is a problem for you and how your use case cannot be satisfied properly using a workaround. -## drop SMB support - -The SMB protocol has weak security and is rarely used these days. - -SMB support gets removed in September 2026. - ## drop NTLM support The NTLM authentication method has weak security and is rarely used these @@ -86,3 +80,4 @@ HTTP/2 Server Push gets removed in March 2027. - NTLM (became opt-in in 8.20.0) - c-ares < 1.16.0 (removed in 8.20.0) - TLS-SRP (removed in 8.22.0) +- SMB support (removed in 8.23.0) From bca466c332e386551c8f5b2429bafec1cd0f8466 Mon Sep 17 00:00:00 2001 From: Viktor Szakats Date: Mon, 28 Sep 2026 23:55:52 +0200 Subject: [PATCH 296/481] apple: shorten versions in `__builtin_available()` calls Drop `.0` suffix. Also to sync with lib/vtls/apple.c. Follow-up to c492678e3d80581231e1b5829cf9ad39f04eadbd #23133 Closes #23150 --- CMake/CurlTests.c | 2 +- acinclude.m4 | 2 +- lib/cf-socket.c | 2 +- lib/socketpair.c | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/CMake/CurlTests.c b/CMake/CurlTests.c index 4d8fa1d1d88f..f6027d0af4f6 100644 --- a/CMake/CurlTests.c +++ b/CMake/CurlTests.c @@ -331,7 +331,7 @@ int main(void) #ifdef HAVE_BUILTIN_AVAILABLE int main(void) { - if(__builtin_available(macOS 10.12, iOS 5.0, *)) {} + if(__builtin_available(macOS 10.12, iOS 5, *)) {} return 0; } #endif diff --git a/acinclude.m4 b/acinclude.m4 index 310d680600c3..bb4ac660d96d 100644 --- a/acinclude.m4 +++ b/acinclude.m4 @@ -1518,7 +1518,7 @@ AC_DEFUN([CURL_SUPPORTS_BUILTIN_AVAILABLE], [ AC_COMPILE_IFELSE([ AC_LANG_PROGRAM([[ ]],[[ - if(__builtin_available(macOS 10.12, iOS 5.0, *)) {} + if(__builtin_available(macOS 10.12, iOS 5, *)) {} ]]) ],[ AC_MSG_RESULT([yes]) diff --git a/lib/cf-socket.c b/lib/cf-socket.c index 08a0d74c5053..bc6edf28c97a 100644 --- a/lib/cf-socket.c +++ b/lib/cf-socket.c @@ -1380,7 +1380,7 @@ static int do_connect(struct Curl_cfilter *cf, struct Curl_easy *data, /* while connectx function is available since macOS 10.11 / iOS 9, it did not have the interface declared correctly until Xcode 9 / macOS SDK 10.13 */ - if(__builtin_available(macOS 10.11, iOS 9.0, tvOS 9.0, watchOS 2.0, *)) { + if(__builtin_available(macOS 10.11, iOS 9, tvOS 9, watchOS 2, *)) { sa_endpoints_t endpoints; endpoints.sae_srcif = 0; endpoints.sae_srcaddr = NULL; diff --git a/lib/socketpair.c b/lib/socketpair.c index d408cfdae295..bcd755a98426 100644 --- a/lib/socketpair.c +++ b/lib/socketpair.c @@ -67,8 +67,8 @@ static int wakeup_pipe(curl_socket_t socks[2], bool nonblocking) __WATCH_OS_VERSION_MAX_ALLOWED >= 270000) || \ (defined(__VISION_OS_VERSION_MAX_ALLOWED) && \ __VISION_OS_VERSION_MAX_ALLOWED >= 270000)) - if(__builtin_available(macOS 27.0, iOS 27.0, tvOS 27.0, watchOS 27.0, - visionOS 27.0, macCatalyst 27.0, *)) + if(__builtin_available(macOS 27, iOS 27, tvOS 27, watchOS 27, visionOS 27, + macCatalyst 27, *)) return pipe2(socks, (nonblocking ? O_NONBLOCK : 0) | O_CLOEXEC) ? -1 : 0; else #endif From 05aea50737dbd549fb2011dadc0d0be1d5678b49 Mon Sep 17 00:00:00 2001 From: Viktor Szakats Date: Mon, 28 Sep 2026 23:49:03 +0200 Subject: [PATCH 297/481] sectrust: add guards for rest of Apple OS flavors Refs: https://developer.apple.com/documentation/security/secpolicycreaterevocation(_:)?language=objc https://developer.apple.com/documentation/security/sectrustevaluatewitherror(_:_:)?language=objc Also: - checked rest of Apple version guards if they support more OS flavors, and did not find anything missing, according to SDK 26 headers and online documentation. Ref: https://github.com/curl/curl/pull/23126#discussion_r4120141037 Closes #23149 --- lib/vtls/apple.c | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/lib/vtls/apple.c b/lib/vtls/apple.c index 9959304f19e3..356cfff9f565 100644 --- a/lib/vtls/apple.c +++ b/lib/vtls/apple.c @@ -53,7 +53,13 @@ #if (defined(__MAC_OS_X_VERSION_MAX_ALLOWED) && \ __MAC_OS_X_VERSION_MAX_ALLOWED >= 101400) || \ (defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && \ - __IPHONE_OS_VERSION_MAX_ALLOWED >= 120000) + __IPHONE_OS_VERSION_MAX_ALLOWED >= 120000) || \ + (defined(__TV_OS_VERSION_MAX_ALLOWED) && \ + __TV_OS_VERSION_MAX_ALLOWED >= 120000) || \ + (defined(__WATCH_OS_VERSION_MAX_ALLOWED) && \ + __WATCH_OS_VERSION_MAX_ALLOWED >= 50000) || \ + (defined(__VISION_OS_VERSION_MAX_ALLOWED) && \ + __VISION_OS_VERSION_MAX_ALLOWED >= 10000) #define SUPPORTS_SecTrustEvaluateWithError 1 #endif @@ -61,7 +67,13 @@ ((defined(__MAC_OS_X_VERSION_MIN_REQUIRED) && \ __MAC_OS_X_VERSION_MIN_REQUIRED >= 101400) || \ (defined(__IPHONE_OS_VERSION_MIN_REQUIRED) && \ - __IPHONE_OS_VERSION_MIN_REQUIRED >= 120000)) + __IPHONE_OS_VERSION_MIN_REQUIRED >= 120000) || \ + (defined(__TV_OS_VERSION_MIN_REQUIRED) && \ + __TV_OS_VERSION_MIN_REQUIRED >= 120000) || \ + (defined(__WATCH_OS_VERSION_MIN_REQUIRED) && \ + __WATCH_OS_VERSION_MIN_REQUIRED >= 50000) || \ + (defined(__VISION_OS_VERSION_MIN_REQUIRED) && \ + __VISION_OS_VERSION_MIN_REQUIRED >= 10000)) #define REQUIRES_SecTrustEvaluateWithError 1 #endif @@ -74,7 +86,13 @@ #if (defined(__MAC_OS_X_VERSION_MAX_ALLOWED) && \ __MAC_OS_X_VERSION_MAX_ALLOWED >= 100900) || \ (defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && \ - __IPHONE_OS_VERSION_MAX_ALLOWED >= 70000) + __IPHONE_OS_VERSION_MAX_ALLOWED >= 70000) || \ + (defined(__TV_OS_VERSION_MAX_ALLOWED) && \ + __TV_OS_VERSION_MAX_ALLOWED >= 90000) || \ + (defined(__WATCH_OS_VERSION_MAX_ALLOWED) && \ + __WATCH_OS_VERSION_MAX_ALLOWED >= 20000) || \ + (defined(__VISION_OS_VERSION_MAX_ALLOWED) && \ + __VISION_OS_VERSION_MAX_ALLOWED >= 10000) #define SUPPORTS_SecOCSP 1 #endif From 00be517b50cae61537f88bc570ce8f96bb2fe806 Mon Sep 17 00:00:00 2001 From: Viktor Szakats Date: Tue, 29 Sep 2026 00:55:30 +0200 Subject: [PATCH 298/481] GHA/linux: reduce require tests to fit flaky stunnel fail ``` [...] TESTINFO: "failed starting HTTPS server (stunnel)" 5 times (300, 2009, 2037, 2038, 2885) [...] TESTDONE: 1798 tests out of 1798 reported OK: 100% TESTFAIL: number of tests run (1798) was below the minimum of: 1800 ``` Ref: https://github.com/curl/curl/actions/runs/36494387049/job/109170407273?pr=23152#step:50:3998 Follow-up to 37a6e61f736973c51aa76241b7bc0c195c6420d7 #23145 --- .github/workflows/linux.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 5f1f77c61e53..80d1fc63712b 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -35,7 +35,7 @@ cache-mode: write env: MAKEFLAGS: -j 5 CURL_CI: github - CURL_TEST_MIN: 1800 + CURL_TEST_MIN: 1790 DO_NOT_TRACK: '1' # renovate: datasource=github-tags depName=awslabs/aws-lc versioning=semver registryUrl=https://github.com AWSLC_VERSION: 5.8.0 From 9a0b2e50ee8489a4271b6b5a993546fe76496eaf Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 28 Sep 2026 22:38:50 +0000 Subject: [PATCH 299/481] GHA: update dependency pyrefly to v1.3.2 Ref: https://github.com/facebook/pyrefly/releases/tag/1.3.2 Closes #23151 --- .github/scripts/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/scripts/requirements.txt b/.github/scripts/requirements.txt index fc537ed0c201..4e815693193d 100644 --- a/.github/scripts/requirements.txt +++ b/.github/scripts/requirements.txt @@ -4,6 +4,6 @@ cmakelang==0.6.13 codespell==2.4.3 -pyrefly==1.3.1 +pyrefly==1.3.2 reuse==6.2.0 ruff==0.16.7 From 5c58f89fff655f275dfac80ee47eda10257f9154 Mon Sep 17 00:00:00 2001 From: Viktor Szakats Date: Tue, 29 Sep 2026 00:42:29 +0200 Subject: [PATCH 300/481] renovate.json: bump pyrefly once per month pyrefly seems to be getting ~weekly releases. Reduce update spam by bumping less often. Ref: https://github.com/facebook/pyrefly Follow-up to 9a0b2e50ee8489a4271b6b5a993546fe76496eaf #23151 Follow-up to f77c574445532e3c17e624fe4965b8aae4a8c45e #19392 Closes #23152 --- renovate.json | 1 + 1 file changed, 1 insertion(+) diff --git a/renovate.json b/renovate.json index 86a85e41eb98..3b01c46ae307 100644 --- a/renovate.json +++ b/renovate.json @@ -49,6 +49,7 @@ "description": "Schedule package updates on the 10th of each month", "matchPackageNames": [ "/codeql/i", + "/pyrefly/i", "/ruff/i" ], "groupName": "monthly updates by name", From 657d7f18cf45b9191d9b8efcccd773cfa73b521a Mon Sep 17 00:00:00 2001 From: yuwk <1729065730@qq.com> Date: Tue, 29 Sep 2026 13:25:33 +0800 Subject: [PATCH 301/481] tests: fix typos in a comment Comment-only spelling fixes in tests/http/test_02_download.py: "transers" -> "transfers", "exloded" -> "exploded". No code change. Closes #23153 --- tests/http/test_02_download.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/http/test_02_download.py b/tests/http/test_02_download.py index 39d837795b08..f4e896b9ccba 100644 --- a/tests/http/test_02_download.py +++ b/tests/http/test_02_download.py @@ -657,8 +657,8 @@ def test_02_34_max_total_conns(self, env: Env, httpd, nghttpx, proto, max_total_ assert n <= max_total_conns assert matched_lines > 0 - # 2 parallel transers, pause and resume. Load a 100 MB zip bomb from - # the server with "Content-Encoding: gzip" that gets exloded during + # 2 parallel transfers, pause and resume. Load a 100 MB zip bomb from + # the server with "Content-Encoding: gzip" that gets exploded during # response writing to the client. Client pauses after 1MB unzipped data # and causes buffers to fill while the server sends more response # data. From 14037d1254e7a8ebbddecc21d96bd7a5a95ccc50 Mon Sep 17 00:00:00 2001 From: Viktor Szakats Date: Tue, 29 Sep 2026 08:31:15 +0200 Subject: [PATCH 302/481] GHA: bump msys2/setup-msys2 to v2.33.0, drop wolfssl workaround Also: - drop explicit wolfSSL package update. - install MSYS2 Perl manually where missing. (no longer installed by default) Ref: https://github.com/msys2/setup-msys2/releases/tag/v2.33.0 Follow-up to d854ab4673c2f9d8048c7f0f6d164b7e4d5e0865 #22580 Follow-up to d054f386dd77ed02f6d7513e0464e414948d93df #22259 Closes #23154 --- .github/workflows/distcheck.yml | 2 +- .github/workflows/windows.yml | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/distcheck.yml b/.github/workflows/distcheck.yml index 49193488a677..1897d255ef18 100644 --- a/.github/workflows/distcheck.yml +++ b/.github/workflows/distcheck.yml @@ -271,7 +271,7 @@ jobs: matrix: image: [ubuntu-26.04-arm, macos-latest, windows-2022] steps: - - uses: msys2/setup-msys2@66cd2cce69caa17b53920067426061ca1de3a884 # v2.32.0 + - uses: msys2/setup-msys2@ec48f7c5447b3140e2b088413ae3a55687bccb6e # v2.33.0 if: ${{ contains(matrix.image, 'windows') }} with: msystem: mingw64 diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index d5e7ab0297b7..a7ae1f4db6ab 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -338,7 +338,7 @@ jobs: shell: bash run: echo '::group::MSYS2 packages'; /c/msys64/usr/bin/pacman.exe -Q || true; echo '::endgroup::' - - uses: msys2/setup-msys2@66cd2cce69caa17b53920067426061ca1de3a884 # v2.32.0 + - uses: msys2/setup-msys2@ec48f7c5447b3140e2b088413ae3a55687bccb6e # v2.33.0 if: ${{ matrix.sys == 'msys' }} with: msystem: ${{ matrix.sys }} @@ -346,7 +346,7 @@ jobs: install: >- gcc ${{ matrix.build }} ${{ matrix.build == 'autotools' && 'make' || 'ninja' }} - diffutils + perl diffutils zlib-devel brotli-devel libzstd-devel @@ -354,13 +354,12 @@ jobs: libpsl-devel ${{ matrix.install }} - - uses: msys2/setup-msys2@66cd2cce69caa17b53920067426061ca1de3a884 # v2.32.0 + - uses: msys2/setup-msys2@ec48f7c5447b3140e2b088413ae3a55687bccb6e # v2.33.0 if: ${{ matrix.sys != 'msys' }} with: msystem: ${{ matrix.sys }} - update: ${{ contains(matrix.install, 'wolfssl') && 'true' || 'false' }} install: >- - diffutils + perl diffutils mingw-w64-${{ matrix.env }}-cc mingw-w64-${{ matrix.env }}-${{ matrix.build }} ${{ matrix.build == 'autotools' && 'make' || '' }} mingw-w64-${{ matrix.env }}-libpsl @@ -658,7 +657,7 @@ jobs: shell: bash run: echo '::group::MSYS2 packages'; /c/msys64/usr/bin/pacman.exe -Q || true; echo '::endgroup::' - - uses: msys2/setup-msys2@66cd2cce69caa17b53920067426061ca1de3a884 # v2.32.0 + - uses: msys2/setup-msys2@ec48f7c5447b3140e2b088413ae3a55687bccb6e # v2.33.0 with: msystem: ${{ matrix.sys }} release: false @@ -989,6 +988,7 @@ jobs: -DCURL_ENABLE_NTLM=ON -DUSE_PROXY_HTTP3=ON - name: 'schannel U' + install-msys2: 'perl' install-vcpkg: 'zlib libssh2[core,zlib]' arch: 'arm64' env: 'clang-aarch64' @@ -1008,7 +1008,7 @@ jobs: shell: bash run: echo '::group::MSYS2 packages'; /c/msys64/usr/bin/pacman.exe -Q || true; echo '::endgroup::' - - uses: msys2/setup-msys2@66cd2cce69caa17b53920067426061ca1de3a884 # v2.32.0 + - uses: msys2/setup-msys2@ec48f7c5447b3140e2b088413ae3a55687bccb6e # v2.33.0 with: msystem: ${{ matrix.arch == 'arm64' && 'clangarm64' || 'ucrt64' }} release: ${{ contains(matrix.image, 'arm') }} From be9e60327625eea381c298c122173e35620f17c0 Mon Sep 17 00:00:00 2001 From: Viktor Szakats Date: Tue, 29 Sep 2026 10:55:03 +0200 Subject: [PATCH 303/481] curlx_fopen: add macro to cast file mode Add `CURLX_FILE_MODE(mode)` macro which casts the value to `mode_t` on 32-bit Android and passes it through as-is on other platforms. Replace existing workaround with it, and apply to the recently reintroduced `fchmod()` call that missed it. Fixing: ``` lib/curl_fopen.c:146:24: error: implicit conversion loses integer precision: 'unsigned int' to 'mode_t' (aka 'unsigned short') [-Wimplicit-int-conversion] 146 | if(fchmod(fd, sb.st_mode) == -1) | ~~~~~~ ~~~^~~~~~~ ``` Ref: https://github.com/curl/curl/actions/runs/36549680315/job/109344511716?pr=23159#step:8:30 Ref: https://android.googlesource.com/platform/bionic/+/refs/tags/ndk-r25c/libc/include/sys/stat.h#86 Reported-by: Marcel Raad Fixes #23157 Follow-up to 83b7ad083d4252c11e7e897436ef073029c65de0 #22843 Follow-up to f0eacd94476b431bcea565f8f94e62e336e4fde0 #12998 Follow-up to 03cb1ff4d629d6110dab787c75c187626d58323d #12395 #12299 Follow-up to 06dc599405fd8a87147eddcdb89dab62ce200d9b #11313 Closes #23158 --- lib/curl_fopen.c | 8 ++------ lib/curlx/win32-fopen.h | 7 +++++++ lib/file.c | 5 +---- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/lib/curl_fopen.c b/lib/curl_fopen.c index 13e1fe4d28b6..a110ca9fc6b7 100644 --- a/lib/curl_fopen.c +++ b/lib/curl_fopen.c @@ -126,13 +126,9 @@ CURLcode Curl_fopen(struct Curl_easy *data, const char *filename, #ifdef _WIN32 fd = curlx_open(tempstore, _O_WRONLY | _O_CREAT | _O_EXCL, _S_IREAD | _S_IWRITE); -#elif (defined(ANDROID) || defined(__ANDROID__)) && \ - (defined(__i386__) || defined(__arm__)) - fd = curlx_open(tempstore, O_WRONLY | O_CREAT | O_EXCL, - (mode_t)(S_IRUSR | S_IWUSR | 0600)); #else fd = curlx_open(tempstore, O_WRONLY | O_CREAT | O_EXCL, - S_IRUSR | S_IWUSR | 0600); + CURLX_FILE_MODE(S_IRUSR | S_IWUSR | 0600)); #endif if(fd == -1) goto fail; @@ -143,7 +139,7 @@ CURLcode Curl_fopen(struct Curl_easy *data, const char *filename, if((curlx_fstat(fd, &nsb) != -1) && (nsb.st_uid == sb.st_uid) && (nsb.st_gid == sb.st_gid)) { /* if the user and group are the same, clone the original mode */ - if(fchmod(fd, sb.st_mode) == -1) + if(fchmod(fd, CURLX_FILE_MODE(sb.st_mode)) == -1) goto fail; } } diff --git a/lib/curlx/win32-fopen.h b/lib/curlx/win32-fopen.h index 469d3c881303..06215ad357f2 100644 --- a/lib/curlx/win32-fopen.h +++ b/lib/curlx/win32-fopen.h @@ -72,6 +72,13 @@ int curlx_win32_rename(const char *oldpath, const char *newpath); #define curlx_rename rename #endif +#if (defined(ANDROID) || defined(__ANDROID__)) && \ + (defined(__i386__) || defined(__arm__)) +#define CURLX_FILE_MODE(x) ((mode_t)(x)) +#else +#define CURLX_FILE_MODE(x) (x) +#endif + #ifdef CURL_MEMDEBUG #define curlx_fopen(file, mode) curl_dbg_fopen(file, mode, __LINE__, __FILE__) #define curlx_freopen(file, mode, fh) \ diff --git a/lib/file.c b/lib/file.c index fd2b703ff585..7fad51d971bf 100644 --- a/lib/file.c +++ b/lib/file.c @@ -334,11 +334,8 @@ static CURLcode file_upload(struct Curl_easy *data, #ifdef _WIN32 fd = curlx_open(file->path, mode, data->set.new_file_perms & (_S_IREAD | _S_IWRITE)); -#elif (defined(ANDROID) || defined(__ANDROID__)) && \ - (defined(__i386__) || defined(__arm__)) - fd = curlx_open(file->path, mode, (mode_t)data->set.new_file_perms); #else - fd = curlx_open(file->path, mode, data->set.new_file_perms); + fd = curlx_open(file->path, mode, CURLX_FILE_MODE(data->set.new_file_perms)); #endif if(fd < 0) { failf(data, "cannot open %s for writing", file->path); From 42ed22d520c75cc7844dcdc169d0519c0020309f Mon Sep 17 00:00:00 2001 From: Viktor Szakats Date: Tue, 29 Sep 2026 11:30:43 +0200 Subject: [PATCH 304/481] GHA/non-native: add 32-bit Android job Taking ~30s CI time. Ref: #23158 Ref: #23157 Closes #23159 --- .github/workflows/non-native.yml | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/.github/workflows/non-native.yml b/.github/workflows/non-native.yml index 3e7bf1abd347..aca4f6246626 100644 --- a/.github/workflows/non-native.yml +++ b/.github/workflows/non-native.yml @@ -375,7 +375,7 @@ jobs: fi android: - name: "Android ${{ matrix.platform }}, ${{ matrix.build == 'cmake' && 'CM' || 'AM' }} ${{ matrix.name }} arm64" + name: "Android ${{ matrix.platform }}, ${{ matrix.build == 'cmake' && 'CM' || 'AM' }} ${{ matrix.name }} ${{ matrix.arch }}" runs-on: ubuntu-latest timeout-minutes: 5 env: @@ -385,16 +385,19 @@ jobs: strategy: matrix: include: - - { build: 'autotools', platform: '21', name: "!ssl !zstd", + - { build: 'autotools', platform: '21', arch: 'arm64-v8a' , name: "!ssl !zstd", options: '--without-ssl --without-libpsl --without-zstd' } - - { build: 'cmake' , platform: '21', name: "!ssl !zstd", + - { build: 'cmake' , platform: '21', arch: 'arm64-v8a' , name: "!ssl !zstd", options: '-DCURL_ENABLE_SSL=OFF -DCURL_USE_LIBPSL=OFF -DCURL_ZSTD=OFF' } - - { build: 'autotools', platform: '35', name: "!ssl !zstd", + - { build: 'cmake' , platform: '21', arch: 'armeabi-v7a', name: "!ssl !zstd", + options: '-DCURL_ENABLE_SSL=OFF -DCURL_USE_LIBPSL=OFF -DCURL_ZSTD=OFF' } + + - { build: 'autotools', platform: '35', arch: 'arm64-v8a' , name: "!ssl !zstd", options: '--without-ssl --without-libpsl --without-zstd' } - - { build: 'cmake' , platform: '35', name: "!ssl !zstd", + - { build: 'cmake' , platform: '35', arch: 'arm64-v8a' , name: "!ssl !zstd", options: '-DCURL_ENABLE_SSL=OFF -DCURL_USE_LIBPSL=OFF -DCURL_ZSTD=OFF' } fail-fast: false @@ -409,25 +412,31 @@ jobs: - name: 'configure' env: + MATRIX_ARCH: '${{ matrix.arch }}' MATRIX_OPTIONS: '${{ matrix.options }}' MATRIX_PLATFORM: '${{ matrix.platform }}' run: | if [ "${MATRIX_BUILD}" = 'cmake' ]; then # https://developer.android.com/ndk/guides/cmake cmake -B bld -G Ninja \ - -DANDROID_ABI=arm64-v8a \ + -DANDROID_ABI="${MATRIX_ARCH}" \ -DANDROID_PLATFORM="android-${MATRIX_PLATFORM}" \ -DCMAKE_TOOLCHAIN_FILE="${ANDROID_NDK_HOME}/build/cmake/android.toolchain.cmake" -DCMAKE_WARN_DEPRECATED=OFF \ -DCMAKE_UNITY_BUILD=ON \ -DCURL_WERROR=ON \ ${MATRIX_OPTIONS} else + if [ "${MATRIX_ARCH}" = 'armeabi-v7a' ]; then + triplet='armv7a-linux-androideabi' + else + triplet='aarch64-linux-android' + fi TOOLCHAIN="${ANDROID_NDK_HOME}/toolchains/llvm/prebuilt/linux-x86_64" mkdir bld && cd bld && ../configure --enable-unity --enable-warnings --enable-werror --disable-shared \ --disable-dependency-tracking --enable-option-checking=fatal \ - CC="$TOOLCHAIN/bin/aarch64-linux-android${MATRIX_PLATFORM}-clang" \ + CC="$TOOLCHAIN/bin/${triplet}${MATRIX_PLATFORM}-clang" \ AR="$TOOLCHAIN/bin/llvm-ar" \ RANLIB="$TOOLCHAIN/bin/llvm-ranlib" \ - --host="aarch64-linux-android${MATRIX_PLATFORM}" \ + --host="${triplet}${MATRIX_PLATFORM}" \ ${MATRIX_OPTIONS} fi From ea21cab2a7e9610f78b432f77246e7a7eb6dfecc Mon Sep 17 00:00:00 2001 From: Viktor Szakats Date: Tue, 29 Sep 2026 12:18:47 +0200 Subject: [PATCH 305/481] schannel: fix compiler warning `-Wstrict-aliasing` in ALPN code Seen in 32-bit `-DCMAKE_BUILD_TYPE=Release` builds, with GCC 6.4.0. Reproduced by switching the dl-mingw 6.4.0 job to Release (from Debug). Fixing: ``` lib/vtls/schannel.c: In function 'schannel_connect_step1': lib/vtls/schannel.c:938:5: error: dereferencing type-punned pointer will break strict-aliasing rules [-Wstrict-aliasing] *(unsigned int *)(void *)&alpn_buffer[cur] = ^ ``` Ref: https://github.com/curl/curl/actions/runs/36553564862/job/109357301399?pr=23161#step:11:177 Cherry-picked from #23161 Closes #23163 --- lib/vtls/schannel.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/vtls/schannel.c b/lib/vtls/schannel.c index 8ffe2db1f6e3..c95e373aabb4 100644 --- a/lib/vtls/schannel.c +++ b/lib/vtls/schannel.c @@ -926,6 +926,7 @@ static CURLcode schannel_connect_step1(struct Curl_cfilter *cf, int list_start_index = 0; unsigned int *extension_len = NULL; unsigned short *list_len = NULL; + const unsigned int uiALPN = SecApplicationProtocolNegotiationExt_ALPN; struct alpn_proto_buf proto; /* The first four bytes is an unsigned int indicating number @@ -935,8 +936,7 @@ static CURLcode schannel_connect_step1(struct Curl_cfilter *cf, /* The next four bytes are an indicator that this buffer contains ALPN data, as opposed to NPN, for example. */ - *(unsigned int *)(void *)&alpn_buffer[cur] = - SecApplicationProtocolNegotiationExt_ALPN; + memcpy(&alpn_buffer[cur], &uiALPN, sizeof(uiALPN)); cur += (int)sizeof(unsigned int); /* The next two bytes is an unsigned short indicating the number From 92c3857c7cbae0f41bfe90b5f737667c992887b3 Mon Sep 17 00:00:00 2001 From: Viktor Szakats Date: Tue, 29 Sep 2026 12:05:03 +0200 Subject: [PATCH 306/481] GHA/windows: merge 32-bit mingw job into the dl-mingw one As the `mingw32` environment is deprecated in MSYS2. Reduce it to a single, standalone job, still using a couple of `mingw32` packages, till we can. Changes: drop building: debug, unicode, unity in 32-bit mingw-w64. Dropping 1 job taking ~2.5 minutes, adding ~half minute to remaining one. Saving 2 minutes overall. It also simplifies things by avoiding errors due to packages missing from `mingw32`, but available in first tier envs. E.g. when adding `libssh` or `wolfssl` to the per-workflow `install:` list. I expect this list to gradually grow. Note, `mingw32` packages are still used in the remaining 32-bit job. Fixing one instance of: ``` Warning: [msystem-mingw32] MINGW32 is deprecated because 32-bit environments are being phased out. There is no direct replacement. To acknowledge and suppress this warning, add 'msystem-mingw32' to 'suppress-deprecation-warnings'. ``` Ref: https://github.com/curl/curl/actions/runs/36551828804/job/109351622601?pr=23155#step:4:27 Follow-up to ea21cab2a7e9610f78b432f77246e7a7eb6dfecc #23163 Follow-up to 14037d1254e7a8ebbddecc21d96bd7a5a95ccc50 #23154 Closes #23161 --- .github/workflows/windows.yml | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index a7ae1f4db6ab..827ee18a25f4 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -328,10 +328,6 @@ jobs: build: 'cmake' , sys: 'ucrt64' , env: 'ucrt-x86_64' , tflags: 'skiprun', config: '-DENABLE_DEBUG=ON -DBUILD_SHARED_LIBS=ON -DCURL_USE_SCHANNEL=ON -DCURL_USE_WOLFSSL=ON -DENABLE_UNICODE=ON -DCURL_DISABLE_HTTPSIG=OFF', install: 'mingw-w64-ucrt-x86_64-libssh2 mingw-w64-ucrt-x86_64-wolfssl' } - - { name: 'MultiSSL R', type: 'Release', - build: 'cmake' , sys: 'mingw32' , env: 'i686' , tflags: 'skiprun', - config: '-DENABLE_DEBUG=OFF -DBUILD_SHARED_LIBS=ON -DCURL_USE_GNUTLS=ON -DCURL_USE_OPENSSL=ON -DCURL_USE_SCHANNEL=ON -DENABLE_ARES=ON -DENABLE_UNICODE=ON', - install: 'mingw-w64-i686-c-ares mingw-w64-i686-gnutls mingw-w64-i686-libssh2 mingw-w64-i686-openssl' } fail-fast: false steps: - name: 'preinstalled tools' @@ -629,16 +625,16 @@ jobs: install: mingw-w64-x86_64-mbedtls type: 'Debug' tflags: 'skiprun' - - name: 'schannel !unity' # mingw-w64 5.0 + - name: 'MultiSSL !unity' # mingw-w64 5.0 sys: 'mingw32' dir: 'mingw32' env: 'i686' ver: '6.4.0' url: 'https://downloads.sourceforge.net/mingw-w64/Toolchains%20targetting%20Win32/Personal%20Builds/mingw-builds/6.4.0/threads-win32/dwarf/i686-6.4.0-release-win32-dwarf-rt_v5-rev0.7z' SHA256: 12d2c62ad4527ec8a52275ea8485678dcbe20bec4716a3c7ba274f225d696085 - config: '-DCURL_USE_SCHANNEL=ON -DENABLE_UNICODE=OFF -DCMAKE_UNITY_BUILD=OFF -DCURL_TARGET_WINDOWS_VERSION=0x0600' - install: perl mingw-w64-i686-libssh2 - type: 'Debug' + config: '-DCURL_USE_GNUTLS=ON -DCURL_USE_OPENSSL=ON -DCURL_USE_SCHANNEL=ON -DENABLE_ARES=ON -DENABLE_UNICODE=OFF -DCMAKE_UNITY_BUILD=OFF -DCURL_TARGET_WINDOWS_VERSION=0x0600' + install: perl mingw-w64-i686-c-ares mingw-w64-i686-gnutls mingw-w64-i686-libssh2 mingw-w64-i686-openssl + type: 'Release' tflags: 'skiprun' - name: 'schannel !examples' # mingw-w64 3.0 sys: 'mingw64' From 2277ef120fb810251f93368285c2ddedb278edae Mon Sep 17 00:00:00 2001 From: Viktor Szakats Date: Tue, 29 Sep 2026 13:31:37 +0200 Subject: [PATCH 307/481] GHA/pkg-install: make `apt-get` less verbose again It seems to mostly work now. (knock on wood) Follow-up to 73d15fcb1a7d7645fdc0626312a9f12aa82cf2ff #22912 Closes #23168 --- .github/actions/pkg-install/action.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/actions/pkg-install/action.yml b/.github/actions/pkg-install/action.yml index ca79d4a0e7f1..5999994045bd 100644 --- a/.github/actions/pkg-install/action.yml +++ b/.github/actions/pkg-install/action.yml @@ -31,8 +31,8 @@ runs: # sudo sed -i 's/priority:1/priority:9/' /etc/apt/apt-mirrors.txt cat /etc/apt/apt-mirrors.txt [[ "${PACKAGES}" = *':i386'* ]] && sudo dpkg --add-architecture i386 - sudo apt-get -o Dpkg::Use-Pty=0 -o Debug::Acquire::http=1 -o Debug::Acquire::https=1 update - sudo apt-get -o Dpkg::Use-Pty=0 -o Debug::Acquire::http=1 -o Debug::Acquire::https=1 install ${PACKAGES} + sudo apt-get -o Dpkg::Use-Pty=0 update + sudo apt-get -o Dpkg::Use-Pty=0 install ${PACKAGES} - name: 'install (brew)' if: ${{ inputs.brew }} From e919448690636c3d31a92ae3115df27adc988669 Mon Sep 17 00:00:00 2001 From: Viktor Szakats Date: Tue, 29 Sep 2026 14:23:23 +0200 Subject: [PATCH 308/481] GHA/windows: suppress MSYS2 deprecation warnings in dl-mingw jobs We keep using them till possible, then possibly drop the dependencies to keep build-testing curl with old GCC + mingw-w64 releases and 32-bit Windows. It's also an option to bump requirements. Follow-up to 14037d1254e7a8ebbddecc21d96bd7a5a95ccc50 #23154 Closes #23170 --- .github/workflows/windows.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 827ee18a25f4..8bc029a500a4 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -656,6 +656,7 @@ jobs: - uses: msys2/setup-msys2@ec48f7c5447b3140e2b088413ae3a55687bccb6e # v2.33.0 with: msystem: ${{ matrix.sys }} + suppress-deprecation-warnings: 'msystem-mingw64 msystem-mingw32' release: false update: false cache: false From da700c7bb70438e7964c240ffd4df9881367434f Mon Sep 17 00:00:00 2001 From: Viktor Szakats Date: Tue, 29 Sep 2026 15:01:08 +0200 Subject: [PATCH 309/481] GHA/windows: reduce MSYS2/Cygwin builds Cygwin and MSYS2 are converging and technically they are the same things, with different packaging, perhaps at different versions. Reduce the 6 jobs to 4, and reduce test runs from 4 to 2, while keeping variations. Saving ~13-14 minutes CI time. Ref: https://www.msys2.org/news/#2025-02-14-moving-msys2-closer-to-cygwin Closes #23172 --- .github/workflows/windows.yml | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 8bc029a500a4..97c79a01db58 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -262,20 +262,12 @@ jobs: include: # MSYS - { name: '!proxy', - build: 'autotools', sys: 'msys' , env: 'x86_64' , tflags: '--min=1750', - config: '--enable-debug --with-openssl --disable-threaded-resolver --disable-proxy --enable-httpsig --enable-ntlm', - install: 'openssl-devel libssh2-devel' } - - { name: 'default', - build: 'autotools', sys: 'msys' , env: 'x86_64' , tflags: 'skiprun', - config: '--enable-debug --with-openssl --disable-threaded-resolver --enable-ntlm', - install: 'openssl-devel libssh2-devel' } - - { name: 'default', - build: 'cmake' , sys: 'msys' , env: 'x86_64' , tflags: '', - config: '-DENABLE_DEBUG=ON -DENABLE_THREADED_RESOLVER=OFF -DCURL_ENABLE_NTLM=ON', + build: 'cmake' , sys: 'msys' , env: 'x86_64' , tflags: 'skiprun', + config: '-DENABLE_DEBUG=ON -DENABLE_THREADED_RESOLVER=OFF -DCURL_DISABLE_PROXY=ON -DCURL_ENABLE_NTLM=ON', install: 'openssl-devel libssh2-devel' } - { name: 'default R', build: 'autotools', sys: 'msys' , env: 'x86_64' , tflags: '', - config: '--with-openssl --enable-ntlm', + config: '--with-openssl --enable-httpsig --enable-ntlm', install: 'openssl-devel libssh2-devel' } # MinGW - { name: 'default', From d48f2f4ec867783e611beeb2ad53fbabcb94aa6f Mon Sep 17 00:00:00 2001 From: Viktor Szakats Date: Tue, 29 Sep 2026 14:40:32 +0200 Subject: [PATCH 310/481] vquic: use `CURLX_FILE_MODE()` in `Curl_qlogdir()` To fix 32-bit Android builds with HTTP/3 enabled. Also: - file: merge win32/non-win32 branches more. Follow-up to be9e60327625eea381c298c122173e35620f17c0 #23158 Closes #23171 --- lib/file.c | 7 ++++--- lib/vquic/vquic.c | 5 +++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/lib/file.c b/lib/file.c index 7fad51d971bf..261801632f54 100644 --- a/lib/file.c +++ b/lib/file.c @@ -331,12 +331,13 @@ static CURLcode file_upload(struct Curl_easy *data, else mode |= O_TRUNC; -#ifdef _WIN32 fd = curlx_open(file->path, mode, - data->set.new_file_perms & (_S_IREAD | _S_IWRITE)); +#ifdef _WIN32 + data->set.new_file_perms & (_S_IREAD | _S_IWRITE) #else - fd = curlx_open(file->path, mode, CURLX_FILE_MODE(data->set.new_file_perms)); + CURLX_FILE_MODE(data->set.new_file_perms) #endif + ); if(fd < 0) { failf(data, "cannot open %s for writing", file->path); return CURLE_WRITE_ERROR; diff --git a/lib/vquic/vquic.c b/lib/vquic/vquic.c index 4a8b42774ace..087074d20b47 100644 --- a/lib/vquic/vquic.c +++ b/lib/vquic/vquic.c @@ -1058,9 +1058,10 @@ CURLcode Curl_qlogdir(struct Curl_easy *data, if(!result) { int qlogfd = curlx_open(curlx_dyn_ptr(&fname), O_WRONLY | O_CREAT | CURL_O_BINARY, - data->set.new_file_perms #ifdef _WIN32 - & (_S_IREAD | _S_IWRITE) + data->set.new_file_perms & (_S_IREAD | _S_IWRITE) +#else + CURLX_FILE_MODE(data->set.new_file_perms) #endif ); if(qlogfd != -1) From e9e60e1ea90e4f1bab1fa7cca49bbaff4cca2ebc Mon Sep 17 00:00:00 2001 From: Viktor Szakats Date: Tue, 29 Sep 2026 16:21:27 +0200 Subject: [PATCH 311/481] test5018: replace literal log dir with macro, to fix `runner.pm` Perl warning Reproducible with `TFLAGS='5018 -j2' ninja tests` using httpsig- and debug-enabled builds. Seen in 'cygwin, CM x86_64 openssl' CI job: ``` Use of uninitialized value in join or string at /cygdrive/d/a/curl/curl/tests/runner.pm line 771. ``` Ref: https://github.com/curl/curl/actions/runs/36575543385/job/109430153389#step:13:4200 Follow-up to a55731050e8c3dbea0b96205cf916443118f6acb #22386 #21239 Closes #23173 --- tests/data/test5018 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/data/test5018 b/tests/data/test5018 index 365a85fb2b23..abbfb5f82d40 100644 --- a/tests/data/test5018 +++ b/tests/data/test5018 @@ -32,14 +32,14 @@ http Debug httpsig - + putdata httpsig: PUT upload -"http://example.com:5800/%TESTNUMBER/resource" --httpsig-algo "ed25519" --httpsig-key @%SRCDIR/data/data-httpsig-ed25519.key --httpsig-keyid "test-key-ed25519" -T log/upload5018 --connect-to example.com:5800:%HOSTIP:%HTTPPORT +"http://example.com:5800/%TESTNUMBER/resource" --httpsig-algo "ed25519" --httpsig-key @%SRCDIR/data/data-httpsig-ed25519.key --httpsig-keyid "test-key-ed25519" -T %LOGDIR/upload5018 --connect-to example.com:5800:%HOSTIP:%HTTPPORT From 098db82d7b794ad3f7a70e976ac34370a7298582 Mon Sep 17 00:00:00 2001 From: Jay Satiro Date: Mon, 28 Sep 2026 13:31:11 -0400 Subject: [PATCH 312/481] Revert "tool_operate: set stdin non-blocking unconditionally" This reverts commit d745b13cad63a13ebd1926a402944fdfe0a0022f. Setting stdin as non-blocking unconditionally caused it to affect future shell commands. There is probably a way to fix it but I think this needs a rethink. For now we revert. Ref: https://github.com/curl/curl/pull/23057 Ref: https://github.com/curl/curl/pull/23081 Reported-by: Daniel S. Roche Researched-by: KayanoLiam@users.noreply.github.com Fixes https://github.com/curl/curl/issues/23127 Closes https://github.com/curl/curl/pull/23144 --- docs/KNOWN_BUGS.md | 7 ------- src/tool_operate.c | 7 +++++-- tests/data/test1548 | 4 ++++ 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/docs/KNOWN_BUGS.md b/docs/KNOWN_BUGS.md index eeec20c76b34..24b38acc74fc 100644 --- a/docs/KNOWN_BUGS.md +++ b/docs/KNOWN_BUGS.md @@ -433,13 +433,6 @@ then subsequently fails anyway if that was actually in use. [curl issue 8112](https://github.com/curl/curl/issues/8112) -## Stdin is not non-blocking on Windows - -The telnet handler reads data from stdin by default without needing to -specify that on the command line, but since Windows is special, reading -from stdin is not done in a non-blocking manner, meaning that curl might -block waiting for input without triggering the timeout. - # HTTP/2 ## HTTP/2 prior knowledge over proxy diff --git a/src/tool_operate.c b/src/tool_operate.c index 7001860f7d6e..b5b3d6ad8c92 100644 --- a/src/tool_operate.c +++ b/src/tool_operate.c @@ -1221,6 +1221,11 @@ static void check_stdin_upload(struct OperationConfig *config, else per->infd = (int)f; #endif + if(curlx_nonblock((curl_socket_t)per->infd, TRUE) < 0) { + char errbuf[STRERROR_LEN]; + warnf("fcntl failed on fd=%d: %s", per->infd, + curlx_strerror(errno, errbuf, sizeof(errbuf))); + } } } @@ -2355,8 +2360,6 @@ static CURLcode run_all_transfers(CURLSH *share, bool orig_noprogress = (bool)global->noprogress; bool orig_isatty = (bool)global->isatty; struct per_transfer *per; - (void)curlx_nonblock((curl_socket_t)STDIN_FILENO, TRUE); - /* fail silently */ /* Time to actually do the transfers */ if(!result) { diff --git a/tests/data/test1548 b/tests/data/test1548 index 42d1ceffcaac..fbc80cb190d5 100644 --- a/tests/data/test1548 +++ b/tests/data/test1548 @@ -23,6 +23,10 @@ telnet TELNET read stdin without upload file + +GET /we/want/%TESTNUMBER HTTP/1.0 + + -m 1 telnet://%HOSTIP:%HTTPPORT From c2e283d5b3343aa49856c276dbe229abdea3d451 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 29 Sep 2026 16:57:56 +0000 Subject: [PATCH 313/481] GHA: update dependency openssl/openssl to v4.0.3 Closes #23174 --- .github/workflows/http3-linux.yml | 2 +- .github/workflows/linux.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/http3-linux.yml b/.github/workflows/http3-linux.yml index 2a8f6e59b1cf..6d7c818eea6a 100644 --- a/.github/workflows/http3-linux.yml +++ b/.github/workflows/http3-linux.yml @@ -50,7 +50,7 @@ env: # renovate: datasource=github-tags depName=libressl/portable versioning=semver registryUrl=https://github.com LIBRESSL_VERSION: 4.3.2 # renovate: datasource=github-releases depName=openssl/openssl versioning=semver extractVersion=^openssl-(?.+)$ registryUrl=https://github.com - OPENSSL_VERSION: 4.0.2 + OPENSSL_VERSION: 4.0.3 # manually bumped OPENSSL_PREV_VERSION: 3.6.2 OPENSSL_PREV_SHA256: aaf51a1fe064384f811daeaeb4ec4dce7340ec8bd893027eee676af31e83a04f diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 80d1fc63712b..76627640dc76 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -55,7 +55,7 @@ env: # handled in renovate.json OPENLDAP_VERSION: 2.6.10 # renovate: datasource=github-releases depName=openssl/openssl versioning=semver extractVersion=^openssl-(?.+)$ registryUrl=https://github.com - OPENSSL_VERSION: 4.0.2 + OPENSSL_VERSION: 4.0.3 # renovate: datasource=github-tags depName=rustls/rustls-ffi versioning=semver registryUrl=https://github.com RUSTLS_VERSION: 0.15.4 # renovate: datasource=github-tags depName=wolfSSL/wolfssl versioning=semver extractVersion=^v?(?.+)-stable$ registryUrl=https://github.com From f1d662dd3a67221001a279b751a1ebe8a6ff023c Mon Sep 17 00:00:00 2001 From: Viktor Szakats Date: Tue, 29 Sep 2026 20:07:49 +0200 Subject: [PATCH 314/481] GHA: sync BoringSSL tarball source with Renovate trigger URL, bump version The original Google source URL is 503 Service Unavailable, but it's best to sync these sources regardless. Follow-up to 4c40f8b8e01d5d851f6d5a2adc3575269d234051 #18386 Fixes #23175 Closes #23176 --- .github/workflows/http3-linux.yml | 5 +++-- .github/workflows/linux.yml | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/.github/workflows/http3-linux.yml b/.github/workflows/http3-linux.yml index 6d7c818eea6a..0bcb38f6f252 100644 --- a/.github/workflows/http3-linux.yml +++ b/.github/workflows/http3-linux.yml @@ -42,7 +42,7 @@ env: # renovate: datasource=github-tags depName=awslabs/aws-lc versioning=semver registryUrl=https://github.com AWSLC_VERSION: 5.8.0 # renovate: datasource=github-tags depName=google/boringssl versioning=semver registryUrl=https://github.com - BORINGSSL_VERSION: 0.20260903.0 + BORINGSSL_VERSION: 0.20260929.0 # renovate: datasource=github-tags depName=gnutls/nettle versioning=semver registryUrl=https://github.com NETTLE_VERSION: 3.10.2 # renovate: datasource=github-tags depName=gnutls/gnutls versioning=semver extractVersion=^nettle_?(?.+)_release_.+$ registryUrl=https://github.com @@ -251,8 +251,9 @@ jobs: mkdir boringssl-src cd boringssl-src curl --disable --fail --silent --show-error --connect-timeout 15 --max-time 120 --retry 6 --retry-connrefused \ - "https://boringssl.googlesource.com/boringssl/+archive/${BORINGSSL_VERSION}.tar.gz" --output pkg.bin + --location --proto-redir =https "https://github.com/google/boringssl/releases/download/${BORINGSSL_VERSION}/boringssl-${BORINGSSL_VERSION}.tar.gz" --output pkg.bin sha256sum pkg.bin && tar -xzf pkg.bin && rm -f pkg.bin + cd "boringssl-${BORINGSSL_VERSION}" cmake -B . -G Ninja -DBUILD_SHARED_LIBS=ON -DBUILD_TESTING=OFF -DCMAKE_INSTALL_PREFIX=/home/runner/boringssl/build cmake --build . cmake --install . diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 76627640dc76..1d350b2652a9 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -40,7 +40,7 @@ env: # renovate: datasource=github-tags depName=awslabs/aws-lc versioning=semver registryUrl=https://github.com AWSLC_VERSION: 5.8.0 # renovate: datasource=github-tags depName=google/boringssl versioning=semver registryUrl=https://github.com - BORINGSSL_VERSION: 0.20260903.0 + BORINGSSL_VERSION: 0.20260929.0 # renovate: datasource=github-releases depName=pizlonator/fil-c versioning=semver-coerced registryUrl=https://github.com FIL_C_VERSION: 0.685 # renovate: datasource=github-tags depName=libressl/portable versioning=semver registryUrl=https://github.com @@ -802,8 +802,9 @@ jobs: mkdir boringssl-src cd boringssl-src curl --disable --fail --silent --show-error --connect-timeout 15 --max-time 120 --retry 6 --retry-connrefused \ - "https://boringssl.googlesource.com/boringssl/+archive/${BORINGSSL_VERSION}.tar.gz" --output pkg.bin + --location --proto-redir =https "https://github.com/google/boringssl/releases/download/${BORINGSSL_VERSION}/boringssl-${BORINGSSL_VERSION}.tar.gz" --output pkg.bin sha256sum pkg.bin && tar -xzf pkg.bin && rm -f pkg.bin + cd "boringssl-${BORINGSSL_VERSION}" cmake -B . -G Ninja -DCMAKE_INSTALL_PREFIX=/home/runner/boringssl -DBUILD_TESTING=OFF -DBUILD_SHARED_LIBS=ON cmake --build . cmake --install . From 5566d7ae4372541be5f0a1e793db4af321c736ac Mon Sep 17 00:00:00 2001 From: Viktor Szakats Date: Tue, 29 Sep 2026 20:26:27 +0200 Subject: [PATCH 315/481] GHA: drop redundant BoringSSL build subdirectory Follow-up to f1d662dd3a67221001a279b751a1ebe8a6ff023c #23176 Closes #23178 --- .github/workflows/http3-linux.yml | 3 +-- .github/workflows/linux.yml | 2 -- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/.github/workflows/http3-linux.yml b/.github/workflows/http3-linux.yml index 0bcb38f6f252..4446b45f0efe 100644 --- a/.github/workflows/http3-linux.yml +++ b/.github/workflows/http3-linux.yml @@ -248,8 +248,7 @@ jobs: - name: 'build boringssl' if: ${{ !steps.cache-boringssl.outputs.cache-hit }} run: | - mkdir boringssl-src - cd boringssl-src + cd ~ curl --disable --fail --silent --show-error --connect-timeout 15 --max-time 120 --retry 6 --retry-connrefused \ --location --proto-redir =https "https://github.com/google/boringssl/releases/download/${BORINGSSL_VERSION}/boringssl-${BORINGSSL_VERSION}.tar.gz" --output pkg.bin sha256sum pkg.bin && tar -xzf pkg.bin && rm -f pkg.bin diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 1d350b2652a9..e746c1d93a14 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -799,8 +799,6 @@ jobs: - name: 'build boringssl' if: ${{ contains(matrix.build.install_steps, 'boringssl') && !steps.cache-boringssl.outputs.cache-hit }} run: | - mkdir boringssl-src - cd boringssl-src curl --disable --fail --silent --show-error --connect-timeout 15 --max-time 120 --retry 6 --retry-connrefused \ --location --proto-redir =https "https://github.com/google/boringssl/releases/download/${BORINGSSL_VERSION}/boringssl-${BORINGSSL_VERSION}.tar.gz" --output pkg.bin sha256sum pkg.bin && tar -xzf pkg.bin && rm -f pkg.bin From 39c8f9dcde7d7fe68dedda6ba7c68cbd305168a6 Mon Sep 17 00:00:00 2001 From: Viktor Szakats Date: Tue, 29 Sep 2026 20:43:30 +0200 Subject: [PATCH 316/481] cmake: enable C++ library for GCC in AWS-LC/BoringSSL static builds Before this patch it was only enabled for clang. To allow building curl against default BoringSSL builds (building static libs), with GCC too. Also: break out C++ library name into a variable. Follow-up to https://github.com/curl/curl/commit/c991902646ba863ccc0c23d8ae6138e9f1f463c1 Cherry-picked from #23177 Closes #23179 --- CMakeLists.txt | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 79cf731f6535..f595e973a78b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -808,6 +808,10 @@ if(_use_core_foundation_and_core_services) list(APPEND CURL_LIBS "-framework CoreServices") endif() +if(NOT DEFINED _curl_stdlib_cxx) + set(_curl_stdlib_cxx "stdc++") +endif() + if(CURL_USE_OPENSSL) find_package(OpenSSL REQUIRED) set(_ssl_enabled ON) @@ -846,8 +850,9 @@ if(CURL_USE_OPENSSL) message(WARNING "AWS-LC/BoringSSL requires POSIX Threads.") endif() endif() - if(OPENSSL_USE_STATIC_LIBS AND CMAKE_C_COMPILER_ID MATCHES "Clang") - list(APPEND CURL_LIBS "stdc++") + if(OPENSSL_USE_STATIC_LIBS AND + (CMAKE_C_COMPILER_ID STREQUAL "GNU" OR CMAKE_C_COMPILER_ID MATCHES "Clang")) + list(APPEND CURL_LIBS "${_curl_stdlib_cxx}") endif() endif() @@ -995,8 +1000,9 @@ macro(curl_openssl_check_exists) if(WIN32) list(APPEND CMAKE_REQUIRED_LIBRARIES "bcrypt") # for OpenSSL/LibreSSL BCryptGenRandom() endif() - if((HAVE_AWSLC OR HAVE_BORINGSSL) AND OPENSSL_USE_STATIC_LIBS AND CMAKE_C_COMPILER_ID MATCHES "Clang") - list(APPEND CMAKE_REQUIRED_LIBRARIES "stdc++") + if((HAVE_AWSLC OR HAVE_BORINGSSL) AND OPENSSL_USE_STATIC_LIBS AND + (CMAKE_C_COMPILER_ID STREQUAL "GNU" OR CMAKE_C_COMPILER_ID MATCHES "Clang")) + list(APPEND CMAKE_REQUIRED_LIBRARIES "${_curl_stdlib_cxx}") endif() endif() if(USE_WOLFSSL) From 17ea04add04e9b4aab6267230140930d24010704 Mon Sep 17 00:00:00 2001 From: Dan Fandrich Date: Mon, 28 Sep 2026 12:06:41 -0700 Subject: [PATCH 317/481] tests/http: use @classmethod when appropriate These methods generally access class variables so this is better than @staticmethod. Closes #23148 --- tests/http/testenv/certs.py | 68 ++++---- tests/http/testenv/env.py | 320 ++++++++++++++++++------------------ 2 files changed, 195 insertions(+), 193 deletions(-) diff --git a/tests/http/testenv/certs.py b/tests/http/testenv/certs.py index 418590679ac6..84fb4065d64f 100644 --- a/tests/http/testenv/certs.py +++ b/tests/http/testenv/certs.py @@ -368,13 +368,13 @@ def create_root(cls, name: str, store_dir: str, key_type: str = "rsa2048") -> Cr store = CertStore(fpath=store_dir) creds = store.load_credentials(name="ca", key_type=key_type, issuer=None) if creds is None: - creds = TestCA._make_ca_credentials(name=name, key_type=key_type) + creds = cls._make_ca_credentials(name=name, key_type=key_type) store.save(creds, name="ca") creds.set_store(store) return creds - @staticmethod - def create_credentials(spec: CertificateSpec, issuer: Credentials, key_type: Any, + @classmethod + def create_credentials(cls, spec: CertificateSpec, issuer: Credentials, key_type: Any, valid_from: timedelta = timedelta(days=-1), valid_to: timedelta = timedelta(days=89), ) -> Credentials: @@ -384,17 +384,17 @@ def create_credentials(spec: CertificateSpec, issuer: Credentials, key_type: Any :returns: the certificate and private key PEM file paths """ if spec.domains and len(spec.domains): - return TestCA._make_server_credentials(name=spec.name, domains=spec.domains, - issuer=issuer, valid_from=valid_from, - valid_to=valid_to, key_type=key_type) + return cls._make_server_credentials(name=spec.name, domains=spec.domains, + issuer=issuer, valid_from=valid_from, + valid_to=valid_to, key_type=key_type) if spec.client: - return TestCA._make_client_credentials(name=spec.name, issuer=issuer, - email=spec.email, valid_from=valid_from, - valid_to=valid_to, key_type=key_type) + return cls._make_client_credentials(name=spec.name, issuer=issuer, + email=spec.email, valid_from=valid_from, + valid_to=valid_to, key_type=key_type) if spec.name: - return TestCA._make_ca_credentials(name=spec.name, issuer=issuer, - valid_from=valid_from, valid_to=valid_to, - key_type=key_type) + return cls._make_ca_credentials(name=spec.name, issuer=issuer, + valid_from=valid_from, valid_to=valid_to, + key_type=key_type) raise CertError(f"unrecognized certificate specification: {spec}") @staticmethod @@ -521,8 +521,8 @@ def _add_client_usages(csr: Any, issuer: Credentials, rfc82name: Optional[str] = ) return cert - @staticmethod - def _make_ca_credentials(name, key_type: Any, + @classmethod + def _make_ca_credentials(cls, name: str, key_type: Any, issuer: Optional[Credentials] = None, valid_from: timedelta = timedelta(days=-1), valid_to: timedelta = timedelta(days=89), @@ -534,46 +534,48 @@ def _make_ca_credentials(name, key_type: Any, else: issuer_subject = None issuer_key = pkey - subject = TestCA._make_x509_name(org_name=name, parent=issuer.subject if issuer else None) - csr = TestCA._make_csr(subject=subject, - issuer_subject=issuer_subject, pkey=pkey, - valid_from_delta=valid_from, valid_until_delta=valid_to) - csr = TestCA._add_ca_usages(csr) + subject = cls._make_x509_name(org_name=name, parent=issuer.subject if issuer else None) + csr = cls._make_csr(subject=subject, + issuer_subject=issuer_subject, pkey=pkey, + valid_from_delta=valid_from, valid_until_delta=valid_to) + csr = cls._add_ca_usages(csr) cert = csr.sign(private_key=issuer_key, algorithm=hashes.SHA256(), backend=default_backend()) return Credentials(name=name, cert=cert, pkey=pkey, issuer=issuer) - @staticmethod - def _make_server_credentials(name: str, domains: List[str], issuer: Credentials, + @classmethod + def _make_server_credentials(cls, + name: str, domains: List[str], issuer: Credentials, key_type: Any, valid_from: timedelta = timedelta(days=-1), valid_to: timedelta = timedelta(days=89), ) -> Credentials: pkey = _private_key(key_type=key_type) - subject = TestCA._make_x509_name(common_name=name, parent=issuer.subject) - csr = TestCA._make_csr(subject=subject, - issuer_subject=issuer.certificate.subject, pkey=pkey, - valid_from_delta=valid_from, valid_until_delta=valid_to) - csr = TestCA._add_leaf_usages(csr, domains=domains, issuer=issuer) + subject = cls._make_x509_name(common_name=name, parent=issuer.subject) + csr = cls._make_csr(subject=subject, + issuer_subject=issuer.certificate.subject, pkey=pkey, + valid_from_delta=valid_from, valid_until_delta=valid_to) + csr = cls._add_leaf_usages(csr, domains=domains, issuer=issuer) cert = csr.sign(private_key=issuer.private_key, algorithm=hashes.SHA256(), backend=default_backend()) return Credentials(name=name, cert=cert, pkey=pkey, issuer=issuer) - @staticmethod - def _make_client_credentials(name: str, + @classmethod + def _make_client_credentials(cls, + name: str, issuer: Credentials, email: Optional[str], key_type: Any, valid_from: timedelta = timedelta(days=-1), valid_to: timedelta = timedelta(days=89), ) -> Credentials: pkey = _private_key(key_type=key_type) - subject = TestCA._make_x509_name(common_name=name, parent=issuer.subject) - csr = TestCA._make_csr(subject=subject, - issuer_subject=issuer.certificate.subject, pkey=pkey, - valid_from_delta=valid_from, valid_until_delta=valid_to) - csr = TestCA._add_client_usages(csr, issuer=issuer, rfc82name=email) + subject = cls._make_x509_name(common_name=name, parent=issuer.subject) + csr = cls._make_csr(subject=subject, + issuer_subject=issuer.certificate.subject, pkey=pkey, + valid_from_delta=valid_from, valid_until_delta=valid_to) + csr = cls._add_client_usages(csr, issuer=issuer, rfc82name=email) cert = csr.sign(private_key=issuer.private_key, algorithm=hashes.SHA256(), backend=default_backend()) diff --git a/tests/http/testenv/env.py b/tests/http/testenv/env.py index 943f4d6480aa..4cc1696de221 100644 --- a/tests/http/testenv/env.py +++ b/tests/http/testenv/env.py @@ -454,245 +454,245 @@ class Env: CONFIG = EnvConfig() - @staticmethod - def setup_incomplete() -> bool: - return not Env.CONFIG.is_complete() + @classmethod + def setup_incomplete(cls) -> bool: + return not cls.CONFIG.is_complete() - @staticmethod - def incomplete_reason() -> Optional[str]: - return Env.CONFIG.get_incomplete_reason() + @classmethod + def incomplete_reason(cls) -> Optional[str]: + return cls.CONFIG.get_incomplete_reason() - @staticmethod - def have_openssl() -> bool: - return Env.CONFIG.openssl is not None + @classmethod + def have_openssl(cls) -> bool: + return cls.CONFIG.openssl is not None - @staticmethod - def have_nghttpx() -> bool: - return Env.CONFIG.nghttpx is not None + @classmethod + def have_nghttpx(cls) -> bool: + return cls.CONFIG.nghttpx is not None - @staticmethod - def have_h3_server() -> bool: - return Env.CONFIG.nghttpx_with_h3 + @classmethod + def have_h3_server(cls) -> bool: + return cls.CONFIG.nghttpx_with_h3 - @staticmethod - def have_h2o() -> bool: - return Env.CONFIG.h2o is not None + @classmethod + def have_h2o(cls) -> bool: + return cls.CONFIG.h2o is not None - @staticmethod - def have_ssl_curl() -> bool: - return Env.curl_has_feature("ssl") or Env.curl_has_feature("multissl") + @classmethod + def have_ssl_curl(cls) -> bool: + return cls.curl_has_feature("ssl") or cls.curl_has_feature("multissl") - @staticmethod - def have_h2_curl() -> bool: - return "http2" in Env.CONFIG.curl_props["features"] + @classmethod + def have_h2_curl(cls) -> bool: + return "http2" in cls.CONFIG.curl_props["features"] - @staticmethod - def have_h3_curl() -> bool: - return "http3" in Env.CONFIG.curl_props["features"] + @classmethod + def have_h3_curl(cls) -> bool: + return "http3" in cls.CONFIG.curl_props["features"] - @staticmethod - def have_compressed_curl() -> bool: + @classmethod + def have_compressed_curl(cls) -> bool: return ( - "brotli" in Env.CONFIG.curl_props["libs"] - or "zlib" in Env.CONFIG.curl_props["libs"] - or "zstd" in Env.CONFIG.curl_props["libs"] + "brotli" in cls.CONFIG.curl_props["libs"] + or "zlib" in cls.CONFIG.curl_props["libs"] + or "zstd" in cls.CONFIG.curl_props["libs"] ) - @staticmethod - def curl_uses_lib(libname: str) -> bool: - return libname.lower() in Env.CONFIG.curl_props["libs"] + @classmethod + def curl_uses_lib(cls, libname: str) -> bool: + return libname.lower() in cls.CONFIG.curl_props["libs"] - @staticmethod - def curl_uses_any_libs(libs: List[str]) -> bool: - return any(libname.lower() in Env.CONFIG.curl_props["libs"] for libname in libs) + @classmethod + def curl_uses_any_libs(cls, libs: List[str]) -> bool: + return any(libname.lower() in cls.CONFIG.curl_props["libs"] for libname in libs) - @staticmethod - def curl_uses_ossl_quic() -> bool: - if Env.have_h3_curl(): - return not Env.curl_uses_lib("ngtcp2") and Env.curl_uses_lib("nghttp3") + @classmethod + def curl_uses_ossl_quic(cls) -> bool: + if cls.have_h3_curl(): + return not cls.curl_uses_lib("ngtcp2") and cls.curl_uses_lib("nghttp3") return False - @staticmethod - def curl_version_string() -> str: - return Env.CONFIG.curl_props["version_string"] + @classmethod + def curl_version_string(cls) -> str: + return cls.CONFIG.curl_props["version_string"] - @staticmethod - def curl_version_at_least(min_version) -> bool: - version = Env.curl_version() - return Env.CONFIG.versiontuple(min_version) <= Env.CONFIG.versiontuple( + @classmethod + def curl_version_at_least(cls, min_version) -> bool: + version = cls.curl_version() + return cls.CONFIG.versiontuple(min_version) <= cls.CONFIG.versiontuple( version ) - @staticmethod - def curl_features_string() -> str: - return Env.CONFIG.curl_props["features_string"] + @classmethod + def curl_features_string(cls) -> str: + return cls.CONFIG.curl_props["features_string"] - @staticmethod - def curl_has_feature(feature: str) -> bool: - return feature.lower() in Env.CONFIG.curl_props["features"] + @classmethod + def curl_has_feature(cls, feature: str) -> bool: + return feature.lower() in cls.CONFIG.curl_props["features"] - @staticmethod - def curl_protocols_string() -> str: - return Env.CONFIG.curl_props["protocols_string"] + @classmethod + def curl_protocols_string(cls) -> str: + return cls.CONFIG.curl_props["protocols_string"] - @staticmethod - def curl_has_protocol(protocol: str) -> bool: - return protocol.lower() in Env.CONFIG.curl_props["protocols"] + @classmethod + def curl_has_protocol(cls, protocol: str) -> bool: + return protocol.lower() in cls.CONFIG.curl_props["protocols"] - @staticmethod - def curl_lib_version(libname: str) -> str: + @classmethod + def curl_lib_version(cls, libname: str) -> str: prefix = f"{libname.lower()}/" - for lversion in Env.CONFIG.curl_props["lib_versions"]: + for lversion in cls.CONFIG.curl_props["lib_versions"]: if lversion.startswith(prefix): return lversion[len(prefix):] return "unknown" - @staticmethod - def curl_lib_version_at_least(libname: str, min_version) -> bool: - lversion = Env.curl_lib_version(libname) + @classmethod + def curl_lib_version_at_least(cls, libname: str, min_version) -> bool: + lversion = cls.curl_lib_version(libname) if lversion != "unknown": - return Env.CONFIG.versiontuple(min_version) <= Env.CONFIG.versiontuple( + return cls.CONFIG.versiontuple(min_version) <= cls.CONFIG.versiontuple( lversion ) return False - @staticmethod - def curl_lib_version_before(libname: str, lib_version) -> bool: - lversion = Env.curl_lib_version(libname) + @classmethod + def curl_lib_version_before(cls, libname: str, lib_version) -> bool: + lversion = cls.curl_lib_version(libname) if lversion != "unknown": if m := re.match(r"(\d+\.\d+\.\d+).*", lversion): lversion = m.group(1) - return Env.CONFIG.versiontuple(lib_version) > Env.CONFIG.versiontuple( + return cls.CONFIG.versiontuple(lib_version) > cls.CONFIG.versiontuple( lversion ) return False - @staticmethod - def curl_os() -> str: - return Env.CONFIG.curl_props["os"] + @classmethod + def curl_os(cls) -> str: + return cls.CONFIG.curl_props["os"] - @staticmethod - def curl_fullname() -> str: - return Env.CONFIG.curl_props["fullname"] + @classmethod + def curl_fullname(cls) -> str: + return cls.CONFIG.curl_props["fullname"] - @staticmethod - def curl_version() -> str: - return Env.CONFIG.curl_props["version"] + @classmethod + def curl_version(cls) -> str: + return cls.CONFIG.curl_props["version"] - @staticmethod - def curl_is_debug() -> bool: - return Env.CONFIG.curl_is_debug + @classmethod + def curl_is_debug(cls) -> bool: + return cls.CONFIG.curl_is_debug - @staticmethod - def curl_is_verbose() -> bool: - return Env.CONFIG.curl_is_verbose + @classmethod + def curl_is_verbose(cls) -> bool: + return cls.CONFIG.curl_is_verbose - @staticmethod - def curl_can_cert_status() -> bool: - return Env.CONFIG.curl_can_cert_status + @classmethod + def curl_can_cert_status(cls) -> bool: + return cls.CONFIG.curl_can_cert_status - @staticmethod - def curl_override_dns() -> bool: - return Env.CONFIG.curl_override_dns + @classmethod + def curl_override_dns(cls) -> bool: + return cls.CONFIG.curl_override_dns - @staticmethod - def curl_resolv_threaded() -> bool: - return Env.CONFIG.curl_resolv_threaded + @classmethod + def curl_resolv_threaded(cls) -> bool: + return cls.CONFIG.curl_resolv_threaded - @staticmethod - def curl_can_doh() -> bool: - return Env.CONFIG.curl_can_doh + @classmethod + def curl_can_doh(cls) -> bool: + return cls.CONFIG.curl_can_doh - @staticmethod - def curl_can_early_data() -> bool: - if Env.curl_uses_lib('gnutls'): - return Env.curl_lib_version_at_least('gnutls', '3.7.2') - return Env.curl_uses_any_libs(['wolfssl', 'quictls', 'openssl']) + @classmethod + def curl_can_early_data(cls) -> bool: + if cls.curl_uses_lib('gnutls'): + return cls.curl_lib_version_at_least('gnutls', '3.7.2') + return cls.curl_uses_any_libs(['wolfssl', 'quictls', 'openssl']) - @staticmethod - def curl_can_h3_early_data() -> bool: - return Env.curl_can_early_data() and Env.curl_uses_lib("ngtcp2") + @classmethod + def curl_can_h3_early_data(cls) -> bool: + return cls.curl_can_early_data() and cls.curl_uses_lib("ngtcp2") - @staticmethod - def http_protos() -> List[str]: + @classmethod + def http_protos(cls) -> List[str]: # http protocols we can test - if Env.have_h2_curl(): - if Env.have_h3(): + if cls.have_h2_curl(): + if cls.have_h3(): return ["http/1.1", "h2", "h3"] return ["http/1.1", "h2"] return ["http/1.1"] - @staticmethod - def http_h1_h2_protos() -> List[str]: + @classmethod + def http_h1_h2_protos(cls) -> List[str]: # http 1+2 protocols we can test - if Env.have_h2_curl(): + if cls.have_h2_curl(): return ["http/1.1", "h2"] return ["http/1.1"] - @staticmethod - def http_mplx_protos() -> List[str]: + @classmethod + def http_mplx_protos(cls) -> List[str]: # http multiplexing protocols we can test - if Env.have_h2_curl(): - if Env.have_h3(): + if cls.have_h2_curl(): + if cls.have_h3(): return ["h2", "h3"] return ["h2"] return [] - @staticmethod - def have_h3() -> bool: - return Env.have_h3_curl() and Env.have_h3_server() + @classmethod + def have_h3(cls) -> bool: + return cls.have_h3_curl() and cls.have_h3_server() - @staticmethod - def httpd_version() -> str: - return Env.CONFIG.httpd_version + @classmethod + def httpd_version(cls) -> str: + return cls.CONFIG.httpd_version - @staticmethod - def nghttpx_version() -> str: - return Env.CONFIG.nghttpx_version + @classmethod + def nghttpx_version(cls) -> str: + return cls.CONFIG.nghttpx_version - @staticmethod - def caddy_version() -> str: - return Env.CONFIG.caddy_version + @classmethod + def caddy_version(cls) -> str: + return cls.CONFIG.caddy_version - @staticmethod - def h2o_version() -> str: - return Env.CONFIG.h2o_version + @classmethod + def h2o_version(cls) -> str: + return cls.CONFIG.h2o_version - @staticmethod - def caddy_is_at_least(minv) -> bool: - return Env.CONFIG.caddy_is_at_least(minv) + @classmethod + def caddy_is_at_least(cls, minv) -> bool: + return cls.CONFIG.caddy_is_at_least(minv) - @staticmethod - def httpd_is_at_least(minv) -> bool: - return Env.CONFIG.httpd_is_at_least(minv) + @classmethod + def httpd_is_at_least(cls, minv) -> bool: + return cls.CONFIG.httpd_is_at_least(minv) - @staticmethod - def has_caddy() -> bool: - return Env.CONFIG.caddy is not None + @classmethod + def has_caddy(cls) -> bool: + return cls.CONFIG.caddy is not None - @staticmethod - def has_vsftpd() -> bool: - return Env.CONFIG.vsftpd is not None + @classmethod + def has_vsftpd(cls) -> bool: + return cls.CONFIG.vsftpd is not None - @staticmethod - def vsftpd_version() -> str: - return Env.CONFIG.vsftpd_version + @classmethod + def vsftpd_version(cls) -> str: + return cls.CONFIG.vsftpd_version - @staticmethod - def has_danted() -> bool: - return Env.CONFIG.danted is not None + @classmethod + def has_danted(cls) -> bool: + return cls.CONFIG.danted is not None - @staticmethod - def has_sshd() -> bool: - return Env.CONFIG.sshd is not None + @classmethod + def has_sshd(cls) -> bool: + return cls.CONFIG.sshd is not None - @staticmethod - def has_sftpd() -> bool: - return Env.has_sshd() and Env.CONFIG.sftpd is not None + @classmethod + def has_sftpd(cls) -> bool: + return cls.has_sshd() and cls.CONFIG.sftpd is not None - @staticmethod - def tcpdump() -> Optional[str]: - return Env.CONFIG.tcpdump + @classmethod + def tcpdump(cls) -> Optional[str]: + return cls.CONFIG.tcpdump def __init__(self, pytestconfig=None, env_config=None): if env_config: From b37b3e5314d90a7533586542ecd3a9fa7357a681 Mon Sep 17 00:00:00 2001 From: Viktor Szakats Date: Tue, 29 Sep 2026 20:23:22 +0200 Subject: [PATCH 318/481] GHA/linux: build and link BoringSSL statically BoringSSL builds static libs by default. Switch to this default and adjust curl build options to link with it correctly, with default GCC. Slight downside is that libcurl must be built statically also, to avoid: ``` /usr/bin/ld: /home/runner/boringssl/lib/libssl.a(extensions.cc.o): warning: relocation against `_ZTVSt18bad_variant_access' in read-only section `.text._ZNSt18bad_variant_accessD2Ev[_ZNSt18bad_variant_accessD5Ev]' /usr/bin/ld: /home/runner/boringssl/lib/libssl.a(extensions.cc.o): relocation R_X86_64_PC32 against symbol `_ZTVSt9exception@@GLIBCXX_3.4' can not be used when making a shared object; recompile with -fPIC /usr/bin/ld: final link failed: bad value ``` (libcurl is already built with `-fPIC`.) BoringSSL remains built and linked as shared library in GHA/http3-linux. Follow-up to 39c8f9dcde7d7fe68dedda6ba7c68cbd305168a6 #23179 Closes #23177 --- .github/workflows/linux.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index e746c1d93a14..4bdf033486be 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -87,7 +87,7 @@ jobs: - name: 'boringssl' install_steps: boringssl pytest - generate: -DOPENSSL_ROOT_DIR=/home/runner/boringssl -DUSE_ECH=ON -DCURL_ENABLE_NTLM=ON + generate: -DOPENSSL_ROOT_DIR=/home/runner/boringssl -DOPENSSL_USE_STATIC_LIBS=ON -DBUILD_SHARED_LIBS=OFF -DUSE_ECH=ON -DCURL_ENABLE_NTLM=ON - name: 'libressl krb5' image: ubuntu-24.04-arm @@ -803,7 +803,7 @@ jobs: --location --proto-redir =https "https://github.com/google/boringssl/releases/download/${BORINGSSL_VERSION}/boringssl-${BORINGSSL_VERSION}.tar.gz" --output pkg.bin sha256sum pkg.bin && tar -xzf pkg.bin && rm -f pkg.bin cd "boringssl-${BORINGSSL_VERSION}" - cmake -B . -G Ninja -DCMAKE_INSTALL_PREFIX=/home/runner/boringssl -DBUILD_TESTING=OFF -DBUILD_SHARED_LIBS=ON + cmake -B . -G Ninja -DCMAKE_INSTALL_PREFIX=/home/runner/boringssl -DBUILD_TESTING=OFF cmake --build . cmake --install . From 2bdaf35e72e1c0ddd4bf572c7b80123c57074cdd Mon Sep 17 00:00:00 2001 From: Stefan Eissing Date: Tue, 29 Sep 2026 12:31:54 +0200 Subject: [PATCH 319/481] peer: compare zoneid case sensitive As interface lookups are case-sensitive, peer compare must also treat those strings as such. Add tests. reported-by: Joshua Rogers Closes https://github.com/curl/curl/pull/23164 --- lib/peer.c | 8 +++++- tests/unit/unit2413.c | 64 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 71 insertions(+), 1 deletion(-) diff --git a/lib/peer.c b/lib/peer.c index a6d9ebcd79ad..b5bf7b7334e0 100644 --- a/lib/peer.c +++ b/lib/peer.c @@ -341,6 +341,12 @@ static bool peer_same_hostname(struct Curl_peer *p1, struct Curl_peer *p2) curl_strequal(p1->hostname, p2->hostname))); } +static bool peer_same_zoneid(const char *z1, const char *z2) +{ + return (z1 == z2) || + (z1 && z2 && !strcmp(z1, z2)); +} + bool Curl_peer_same_destination(struct Curl_peer *p1, struct Curl_peer *p2) { return (p1 == p2) || @@ -348,7 +354,7 @@ bool Curl_peer_same_destination(struct Curl_peer *p1, struct Curl_peer *p2) (p1->port == p2->port) && peer_same_hostname(p1, p2) && (p1->scopeid == p2->scopeid) && - (p1->scopeid || curl_strequal(p1->zoneid, p2->zoneid))); + (p1->scopeid || peer_same_zoneid(p1->zoneid, p2->zoneid))); } CURLcode Curl_peer_from_url(CURLU *uh, struct Curl_easy *data, diff --git a/tests/unit/unit2413.c b/tests/unit/unit2413.c index 2e653c1309e6..362936b40972 100644 --- a/tests/unit/unit2413.c +++ b/tests/unit/unit2413.c @@ -97,6 +97,51 @@ static CURLcode test_create2413(const char *name, return result; } +static CURLcode t2413_cmp(const char *name, + CURL *curl, + const struct Curl_scheme *scheme1, + const char *hostname1, + uint16_t port1, + const struct Curl_scheme *scheme2, + const char *hostname2, + uint16_t port2, + bool exp_equal, + bool exp_same_dest) +{ + struct Curl_peer *peer1 = NULL; + struct Curl_peer *peer2 = NULL; + CURLcode result; + + result = Curl_peer_create((struct Curl_easy *)curl, + scheme1, hostname1, port1, &peer1); + if(result) { + curl_mfprintf(stderr, "%s: create peer1 failed %d", name, (int)result); + goto out; + } + result = Curl_peer_create((struct Curl_easy *)curl, + scheme2, hostname2, port2, &peer2); + if(result) { + curl_mfprintf(stderr, "%s: create peer2 failed %d", name, (int)result); + goto out; + } + + result = CURLE_FAILED_INIT; + if(exp_equal != Curl_peer_equal(peer1, peer2)) + curl_mfprintf(stderr, "%s: equal check failed, not %s", name, + exp_equal ? "equal" : "different"); + else if(exp_same_dest != Curl_peer_same_destination(peer1, peer2)) + curl_mfprintf(stderr, "%s: same destination, not %s", name, + exp_equal ? "the same" : "different"); + else + result = CURLE_OK; + +out: + Curl_peer_unlink(&peer1); + Curl_peer_unlink(&peer2); + fail_unless(!result, "check failed"); + return result; +} + static uint32_t t2413_scopeid(const char *zone) { #ifdef HAVE_IF_NAMETOINDEX @@ -138,6 +183,25 @@ static CURLcode test_unit2413(const char *arg) test_create2413("peer9", curl, &Curl_scheme_https, "::1%123x", 1234, "::1", TRUE, "123x", t2413_scopeid("123x")); + t2413_cmp("cmp1", curl, + &Curl_scheme_https, "test.curl.se", 1234, + &Curl_scheme_https, "test.curl.se", 1234, TRUE, TRUE); + t2413_cmp("cmp2", curl, + &Curl_scheme_https, "Test.curl.se", 1234, + &Curl_scheme_https, "test.curl.se", 1234, TRUE, TRUE); + t2413_cmp("cmp3", curl, + &Curl_scheme_http, "test.curl.se", 1234, + &Curl_scheme_https, "test.curl.se", 1234, FALSE, TRUE); + t2413_cmp("cmp4", curl, + &Curl_scheme_https, "test.curl.se", 1234, + &Curl_scheme_https, "test.curl.se", 443, FALSE, FALSE); + t2413_cmp("cmp5", curl, + &Curl_scheme_https, "[::1%tada]", 443, + &Curl_scheme_https, "[::1%tada]", 443, TRUE, TRUE); + t2413_cmp("cmp6", curl, + &Curl_scheme_https, "[::1%tada]", 443, + &Curl_scheme_https, "[::1%Tada]", 443, FALSE, FALSE); + curl_easy_cleanup(curl); curl_global_cleanup(); From bb348b35e84c40c9564783d7585762b65038e8c5 Mon Sep 17 00:00:00 2001 From: Stefan Eissing Date: Tue, 29 Sep 2026 13:15:40 +0200 Subject: [PATCH 320/481] multi_ntfy: recheck callback on dispatch Since a callback might unregister itself in a call, check the presence of the notify callback every time when looping of the events to dispatch. Removing the callback during dispatch will then simply clear all notification chunks. reported-by: Joshua Rogers Closes https://github.com/curl/curl/pull/23167 --- lib/multi_ntfy.c | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/lib/multi_ntfy.c b/lib/multi_ntfy.c index 2d30ea8d64c3..4723c8025bdd 100644 --- a/lib/multi_ntfy.c +++ b/lib/multi_ntfy.c @@ -108,20 +108,19 @@ static void mntfy_chunk_dispatch_all(struct Curl_multi *multi, struct mntfy_entry *e; struct Curl_easy *data; - if(multi->ntfy.ntfy_cb) { - while((chunk->r_offset < chunk->w_offset) && !multi->ntfy.failure) { - e = &chunk->entries[chunk->r_offset]; - data = e->mid ? Curl_multi_get_easy(multi, e->mid) : multi->admin; - /* only when notification has not been disabled in the meantime */ - if(data && (multi->ntfy.flags & CURL_MNTFY_TYPE_FLAG(e->type))) { - /* this may cause new notifications to be added! */ - CURL_TRC_M(multi->admin, "[NTFY] dispatch %u to xfer %u", - e->type, e->mid); - multi->ntfy.ntfy_cb(multi, e->type, data, multi->ntfy.ntfy_cb_data); - } - /* once dispatched, safe to increment */ - chunk->r_offset++; + while(multi->ntfy.ntfy_cb && + (chunk->r_offset < chunk->w_offset) && !multi->ntfy.failure) { + e = &chunk->entries[chunk->r_offset]; + data = e->mid ? Curl_multi_get_easy(multi, e->mid) : multi->admin; + /* only when notification has not been disabled in the meantime */ + if(data && (multi->ntfy.flags & CURL_MNTFY_TYPE_FLAG(e->type))) { + /* this may cause new notifications to be added! */ + CURL_TRC_M(multi->admin, "[NTFY] dispatch %u to xfer %u", + e->type, e->mid); + multi->ntfy.ntfy_cb(multi, e->type, data, multi->ntfy.ntfy_cb_data); } + /* once dispatched, safe to increment */ + chunk->r_offset++; } mnfty_chunk_reset(chunk); } From a418f3f71b99c6586c7bff6b213154e699f5f57d Mon Sep 17 00:00:00 2001 From: Viktor Szakats Date: Wed, 30 Sep 2026 11:11:48 +0200 Subject: [PATCH 321/481] GHA/linux: disable MD5 SSH tests with Homebrew libssh2 Homebrew libssh2 1.11.1_6 revision backported a patch which disabled legacy algorithms by default, without re-enabling them manually. This effectively disabled MD5, breaking runtests relying on it. Fix by disabling these tests when using Homebrew libssh2. Fixing, in 'CM address-sanitizer H3 c-ares' CI job: ``` FAIL 664: 'SFTP correct host key' SFTP, server key check FAIL 665: 'SCP correct host key' SCP, server key check FAIL 2007: 'SFTP quote rmdir command with trailing junk' SFTP, --quote ``` Ref: https://github.com/curl/curl/actions/runs/36691471709/job/109812874116#step:50:4471 Refs: https://github.com/libssh2/libssh2/commit/b89858b83d68d7e29e0c5b0bb803f8a68271710c.patch https://github.com/Homebrew/homebrew-core/commit/ebe712bba4798a25052a299f67918681630660c4 https://github.com/Homebrew/homebrew-core/pull/311239 Bug: https://github.com/curl/curl/pull/23181#issuecomment-5907854377 Closes #23182 --- .github/workflows/linux.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 4bdf033486be..f1f3ce302006 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -404,6 +404,7 @@ jobs: install_packages: clang-22 libubsan1 libasan8 libtsan2 install_steps: pytest install_steps_brew: openssl libssh2 libngtcp2 libnghttp3 c-ares + tflags: '!664 !665 !2007' # MD5 support is disabled in Homebrew since libssh2 1.11.1_6 CC: clang-22 CFLAGS: >- -fsanitize=address,bounds,leak,signed-integer-overflow,undefined From 183ea8bbedd6e378ed9496049577e1b519bcae3c Mon Sep 17 00:00:00 2001 From: Viktor Szakats Date: Wed, 30 Sep 2026 11:24:34 +0200 Subject: [PATCH 322/481] test2007: bump to use SHA256 public key hash (was: MD5) The hash is not material to the test itself, use the modern alternative to allow running it with MD5 support disabled. Follow-up to a418f3f71b99c6586c7bff6b213154e699f5f57d #23182 Follow-up to 53a3b2114a9b0652bb464437d38f5c8abffc708d #20883 Closes #23184 --- .github/workflows/linux.yml | 2 +- tests/data/test2007 | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index f1f3ce302006..e65034d2829d 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -404,7 +404,7 @@ jobs: install_packages: clang-22 libubsan1 libasan8 libtsan2 install_steps: pytest install_steps_brew: openssl libssh2 libngtcp2 libnghttp3 c-ares - tflags: '!664 !665 !2007' # MD5 support is disabled in Homebrew since libssh2 1.11.1_6 + tflags: '!664 !665' # MD5 support is disabled in Homebrew since libssh2 1.11.1_6 CC: clang-22 CFLAGS: >- -fsanitize=address,bounds,leak,signed-integer-overflow,undefined diff --git a/tests/data/test2007 b/tests/data/test2007 index db82ec3105c0..4806dfd449f6 100644 --- a/tests/data/test2007 +++ b/tests/data/test2007 @@ -20,7 +20,7 @@ sftp SFTP quote rmdir command with trailing junk ---hostpubmd5 %SSHSRVMD5 --key %LOGDIR/server/curl_client_key --pubkey %LOGDIR/server/curl_client_key.pub -u %USER: sftp://%HOSTIP:%SSHPORT%SFTP_PWD/%LOGDIR/file%TESTNUMBER.txt -Q "mkdir /tmp/test trailing_garbage" +--hostpubsha256 %SSHSRVSHA256 --key %LOGDIR/server/curl_client_key --pubkey %LOGDIR/server/curl_client_key.pub -u %USER: sftp://%HOSTIP:%SSHPORT%SFTP_PWD/%LOGDIR/file%TESTNUMBER.txt -Q "mkdir /tmp/test trailing_garbage" From 69d924c72cc8e4062ed3aeaa6a8b7c525626153c Mon Sep 17 00:00:00 2001 From: Viktor Szakats Date: Wed, 30 Sep 2026 10:15:49 +0200 Subject: [PATCH 323/481] GHA/checksrc: install pyrefly via brew to save manual bumps pyrefly does not tend to "break" Python code, thus remove the pin and let it auto-update via Linuxbrew to avoid the disuption of manual version bumps. Assisted-by: Dan Fandrich Ref: https://github.com/curl/curl/pull/23152#issuecomment-5896218881 Follow-up to 5c58f89fff655f275dfac80ee47eda10257f9154 #23152 Closes #23180 --- .github/scripts/requirements.txt | 1 - .github/workflows/checksrc.yml | 12 ++++++------ renovate.json | 1 - 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/.github/scripts/requirements.txt b/.github/scripts/requirements.txt index 4e815693193d..1eebdec71e41 100644 --- a/.github/scripts/requirements.txt +++ b/.github/scripts/requirements.txt @@ -4,6 +4,5 @@ cmakelang==0.6.13 codespell==2.4.3 -pyrefly==1.3.2 reuse==6.2.0 ruff==0.16.7 diff --git a/.github/workflows/checksrc.yml b/.github/workflows/checksrc.yml index f9f84c4a7237..c5c7dfc960d1 100644 --- a/.github/workflows/checksrc.yml +++ b/.github/workflows/checksrc.yml @@ -67,12 +67,12 @@ jobs: background: true run: scripts/perlcheck.sh - - name: 'install typos-cli' + - name: 'install brew packages' background: true timeout-minutes: 2 uses: $/.github/actions/pkg-install with: - brew: typos-cli + brew: pyrefly typos-cli - name: 'install prereqs' uses: $/.github/actions/pkg-install @@ -99,16 +99,16 @@ jobs: source ~/venv/bin/activate scripts/pythonlint.sh + - name: 'wait-all' + wait-all: + - name: 'pyrefly' background: true run: | - source ~/venv/bin/activate + eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)" cd tests/http/ pyrefly check --python-version=3.8 --preset=basic --check-unannotated-defs --summary - - name: 'wait-all' - wait-all: - - name: filter off code from all C and H files run: | git ls-files -z '**.[ch]' | xargs -0 -r -n1 .github/scripts/c-strip diff --git a/renovate.json b/renovate.json index 3b01c46ae307..86a85e41eb98 100644 --- a/renovate.json +++ b/renovate.json @@ -49,7 +49,6 @@ "description": "Schedule package updates on the 10th of each month", "matchPackageNames": [ "/codeql/i", - "/pyrefly/i", "/ruff/i" ], "groupName": "monthly updates by name", From 5e654e28f528462d68239280d216358ce6381d82 Mon Sep 17 00:00:00 2001 From: Viktor Szakats Date: Tue, 29 Sep 2026 08:20:04 +0200 Subject: [PATCH 324/481] GHA/windows: reduce number MSYS2 package cache entries Each `install:` combination results in a separate (and fairly large) cache entry. There are ~12 of them before this commit, in the 100-200 MB size range each. Reduce the number of cache entries by installing commonly used packages for all jobs. But, each installed package comes with a size overhead in the cache entry and a significant time to install them, even if unused in an actual job (because the cache holds the raw package tarballs, which still need to be installed in each job run). Thus, keep the list of commonly used packages minimal, and install the rest of the packages in an separate `pacman` step, without using the cache. These packages include the build tools (autotools, cmake), and extra, not commonly used packages that a job may need in addition. This reduces the number of cache blobs to 4, one per MSYS2 environment, while a slight, tolerable rise in install times. Since some packages are now installed, but not intended to be used, we need to make sure auto-detections don't pick them up. (existing configs already complied with this.) Also: - update logic that relied on the per-package `install:` list to decide which SSH backend is used. Use the `config:` line instead. - tried installing builds tools in the default install list. This turned out to cause much overhead in wasted size and install time. All in all, this balancing act is painful and difficult to maintain, I wish the MSYS2 Action would cache the _installed_ directory, thus avoiding the lengthy install step for installed/cached packages. This would allow to install everything from cache with good performance, still save bandwidth and server load for MSYS2, while simplifying things. Pending upstream issue about such feature: https://github.com/msys2/setup-msys2/issues/575 Slight downside is that mutually exclusive packages cannot be installed, namely libssh2 vs. libssh2-wincng. Not currently used. In the future (once the WinCNG stabilizing fix reaches a release) the variants can be spread across envs to cover both without adding new cache blobs: ```diff -mingw-w64-${{ matrix.env }}-libssh2 +${{ matrix.env == 'ucrt-x86_64' && format('mingw-w64-{0}-libssh2-wincng', matrix.env) || format('mingw-w64-{0}-libssh2', matrix.env) }} ``` Follow-up to 66313cc036671cd4d3e72db65a79a715c7b8f154 #16629 Closes #23155 --- .github/workflows/windows.yml | 85 ++++++++++++++++++++--------------- 1 file changed, 49 insertions(+), 36 deletions(-) diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 97c79a01db58..52ef56a5d866 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -254,6 +254,7 @@ jobs: LDFLAGS: -s MAKEFLAGS: -j 5 MATRIX_BUILD: '${{ matrix.build }}' + MATRIX_ENV: '${{ matrix.env }}' MATRIX_OPENSSH: '${{ matrix.openssh }}' MATRIX_SYS: '${{ matrix.sys }}' MATRIX_TEST: '${{ matrix.test }}' @@ -263,34 +264,30 @@ jobs: # MSYS - { name: '!proxy', build: 'cmake' , sys: 'msys' , env: 'x86_64' , tflags: 'skiprun', - config: '-DENABLE_DEBUG=ON -DENABLE_THREADED_RESOLVER=OFF -DCURL_DISABLE_PROXY=ON -DCURL_ENABLE_NTLM=ON', - install: 'openssl-devel libssh2-devel' } + config: '-DENABLE_DEBUG=ON -DENABLE_THREADED_RESOLVER=OFF -DCURL_DISABLE_PROXY=ON -DCURL_ENABLE_NTLM=ON' } - { name: 'default R', build: 'autotools', sys: 'msys' , env: 'x86_64' , tflags: '', config: '--with-openssl --enable-httpsig --enable-ntlm', - install: 'openssl-devel libssh2-devel' } + install: 'diffutils' } # for tests # MinGW - { name: 'default', build: 'autotools', sys: 'clang64' , env: 'clang-x86_64' , tflags: 'skiprun', - config: '--enable-debug --with-openssl --disable-threaded-resolver --enable-static --without-zlib', - install: 'mingw-w64-clang-x86_64-openssl mingw-w64-clang-x86_64-libssh2' } + config: '--enable-debug --with-openssl --disable-threaded-resolver --enable-static --without-zlib' } - { name: 'wolfssl c-ares U', build: 'autotools', sys: 'ucrt64' , env: 'ucrt-x86_64' , tflags: '', config: '--enable-debug --with-wolfssl --enable-windows-unicode --enable-ares --enable-static --disable-shared --enable-ca-native --enable-ntlm', - install: 'mingw-w64-ucrt-x86_64-c-ares mingw-w64-ucrt-x86_64-nghttp3 mingw-w64-ucrt-x86_64-libssh2 mingw-w64-ucrt-x86_64-wolfssl' } + install: 'mingw-w64-ucrt-x86_64-wolfssl' } - { name: 'schannel c-ares U', type: 'Debug', build: 'cmake' , sys: 'clang64' , env: 'clang-x86_64' , tflags: '--min=1850', config: '-DENABLE_DEBUG=ON -DBUILD_SHARED_LIBS=OFF -DCURL_USE_SCHANNEL=ON -DENABLE_UNICODE=ON -DENABLE_ARES=ON -DCURL_DROP_UNUSED=ON', - install: 'mingw-w64-clang-x86_64-c-ares mingw-w64-clang-x86_64-libssh2 mingw-w64-clang-x86_64-python' } + install: 'mingw-w64-clang-x86_64-python' } # for dict and telnet tests # MinGW torture - { name: 'schannel U torture 1', type: 'Debug', build: 'cmake' , sys: 'ucrt64' , env: 'ucrt-x86_64' , tflags: '-t --shallow=13 --subset=0/2 --min=920', - config: '-DENABLE_DEBUG=ON -DBUILD_SHARED_LIBS=OFF -DCURL_USE_SCHANNEL=ON -DENABLE_UNICODE=ON -DENABLE_ARES=ON', - install: 'mingw-w64-ucrt-x86_64-c-ares mingw-w64-ucrt-x86_64-libssh2' } + config: '-DENABLE_DEBUG=ON -DBUILD_SHARED_LIBS=OFF -DCURL_USE_SCHANNEL=ON -DENABLE_UNICODE=ON -DENABLE_ARES=ON' } - { name: 'schannel U torture 2', type: 'Debug', build: 'cmake' , sys: 'ucrt64' , env: 'ucrt-x86_64' , tflags: '-t --shallow=13 --subset=1/2 --min=900', - config: '-DENABLE_DEBUG=ON -DBUILD_SHARED_LIBS=OFF -DCURL_USE_SCHANNEL=ON -DENABLE_UNICODE=ON -DENABLE_ARES=ON', - install: 'mingw-w64-ucrt-x86_64-c-ares mingw-w64-ucrt-x86_64-libssh2' } + config: '-DENABLE_DEBUG=ON -DBUILD_SHARED_LIBS=OFF -DCURL_USE_SCHANNEL=ON -DENABLE_UNICODE=ON -DENABLE_ARES=ON' } # WARNING: libssh uses hard-coded world-writable paths (C:ProgramData/, /etc/..., ~/.ssh/) # to read its configuration from, making it vulnerable to attacks on # Windows. Do not use this component till there is a fix for these. @@ -299,27 +296,23 @@ jobs: - { name: 'gnutls libssh', type: 'Debug', openssh: 'OpenSSH-Windows', build: 'cmake' , sys: 'clang64' , env: 'clang-x86_64' , tflags: '', config: '-DENABLE_DEBUG=ON -DBUILD_SHARED_LIBS=OFF -DCURL_USE_GNUTLS=ON -DENABLE_UNICODE=OFF -DUSE_NGTCP2=ON -DCURL_USE_LIBSSH2=OFF -DCURL_USE_LIBSSH=ON -DCURL_ENABLE_NTLM=ON', - install: 'mingw-w64-clang-x86_64-gnutls mingw-w64-clang-x86_64-nghttp3 mingw-w64-clang-x86_64-ngtcp2 mingw-w64-clang-x86_64-libssh unzip' } + install: 'mingw-w64-clang-x86_64-gnutls mingw-w64-clang-x86_64-libssh unzip' } - { name: 'schannel R', type: 'Release', image: 'windows-11-arm', build: 'cmake' , sys: 'clangarm64', env: 'clang-aarch64', tflags: 'skiprun', - config: '-DENABLE_DEBUG=OFF -DBUILD_SHARED_LIBS=ON -DCURL_USE_SCHANNEL=ON -DENABLE_UNICODE=ON -DCURL_DROP_UNUSED=ON', - install: 'mingw-w64-clang-aarch64-libssh2' } + config: '-DENABLE_DEBUG=OFF -DBUILD_SHARED_LIBS=ON -DCURL_USE_SCHANNEL=ON -DENABLE_UNICODE=ON -DCURL_DROP_UNUSED=ON' } - { name: 'openssl', type: 'Debug', chkprefill: '_chkprefill', build: 'cmake' , sys: 'clang64' , env: 'clang-x86_64' , tflags: 'skiprun', - config: '-DENABLE_DEBUG=ON -DBUILD_SHARED_LIBS=OFF -DCURL_USE_OPENSSL=ON -DENABLE_UNICODE=OFF -DUSE_NGTCP2=ON -DUSE_PROXY_HTTP3=ON', - install: 'mingw-w64-clang-x86_64-openssl mingw-w64-clang-x86_64-nghttp3 mingw-w64-clang-x86_64-ngtcp2 mingw-w64-clang-x86_64-libssh2' } + config: '-DENABLE_DEBUG=ON -DBUILD_SHARED_LIBS=OFF -DCURL_USE_OPENSSL=ON -DENABLE_UNICODE=OFF -DUSE_NGTCP2=ON -DUSE_PROXY_HTTP3=ON' } - { name: 'openssl', type: 'Release', test: 'uwp', build: 'cmake' , sys: 'ucrt64' , env: 'ucrt-x86_64' , tflags: 'skiprun', - config: '-DBUILD_SHARED_LIBS=ON -DCURL_USE_OPENSSL=ON', - install: 'mingw-w64-ucrt-x86_64-openssl mingw-w64-ucrt-x86_64-libssh2' } + config: '-DBUILD_SHARED_LIBS=ON -DCURL_USE_OPENSSL=ON' } # { name: 'schannel', type: 'Release', test: 'uwp', # build: 'autotools', sys: 'ucrt64' , env: 'ucrt-x86_64' , tflags: 'skiprun', - # config: '--without-debug --with-schannel --disable-static', - # install: 'mingw-w64-ucrt-x86_64-libssh2' } + # config: '--without-debug --with-schannel --disable-static' } - { name: 'schannel dev debug', type: 'Debug', cppflags: '-DCURL_SCHANNEL_DEV_DEBUG', image: 'windows-2025', build: 'cmake' , sys: 'ucrt64' , env: 'ucrt-x86_64' , tflags: 'skiprun', config: '-DENABLE_DEBUG=ON -DBUILD_SHARED_LIBS=ON -DCURL_USE_SCHANNEL=ON -DCURL_USE_WOLFSSL=ON -DENABLE_UNICODE=ON -DCURL_DISABLE_HTTPSIG=OFF', - install: 'mingw-w64-ucrt-x86_64-libssh2 mingw-w64-ucrt-x86_64-wolfssl' } + install: 'mingw-w64-ucrt-x86_64-wolfssl' } fail-fast: false steps: - name: 'preinstalled tools' @@ -331,27 +324,48 @@ jobs: with: msystem: ${{ matrix.sys }} # https://packages.msys2.org/search + # Packages that are used by most jobs. They each inflate cache size and come with a per-job install overhead. + # Add individual package requirements to the matrix `install:` entry instead. install: >- - gcc - ${{ matrix.build }} ${{ matrix.build == 'autotools' && 'make' || 'ninja' }} - perl diffutils - zlib-devel - brotli-devel - libzstd-devel - libnghttp2-devel - libpsl-devel - ${{ matrix.install }} + gcc perl + zlib-devel brotli-devel libzstd-devel libpsl-devel libnghttp2-devel + openssl-devel libssh2-devel - uses: msys2/setup-msys2@ec48f7c5447b3140e2b088413ae3a55687bccb6e # v2.33.0 if: ${{ matrix.sys != 'msys' }} with: msystem: ${{ matrix.sys }} + # Packages that are used by most jobs. They each inflate cache size and come with a per-job install overhead. + # Add individual package requirements to the matrix `install:` entry instead. install: >- perl diffutils mingw-w64-${{ matrix.env }}-cc - mingw-w64-${{ matrix.env }}-${{ matrix.build }} ${{ matrix.build == 'autotools' && 'make' || '' }} mingw-w64-${{ matrix.env }}-libpsl - ${{ matrix.install }} + mingw-w64-${{ matrix.env }}-libssh2 + mingw-w64-${{ matrix.env }}-c-ares + mingw-w64-${{ matrix.env }}-nghttp3 + mingw-w64-${{ matrix.env }}-ngtcp2 + mingw-w64-${{ matrix.env }}-openssl + + - name: 'install prereqs' + env: + MATRIX_INSTALL: '${{ matrix.install }}' + run: | + # Install build tools in a separate step to avoid inflating shared cache size and being installed even if unused. + if [ "${MATRIX_SYS}" = 'msys' ]; then + if [ "${MATRIX_BUILD}" = 'cmake' ]; then + pkgs='cmake ninja' + else + pkgs='autotools' + fi + else + if [ "${MATRIX_BUILD}" = 'cmake' ]; then + pkgs="mingw-w64-${MATRIX_ENV}-cmake" + else + pkgs="mingw-w64-${MATRIX_ENV}-autotools" + fi + fi + /usr/bin/pacman --noconfirm --noprogressbar --sync --needed ${pkgs} ${MATRIX_INSTALL} - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: @@ -368,7 +382,6 @@ jobs: CPPFLAGS: '${{ matrix.cppflags }}' MATRIX_CHKPREFILL: '${{ matrix.chkprefill }}' MATRIX_CONFIG: '${{ matrix.config }}' - MATRIX_ENV: '${{ matrix.env }}' MATRIX_TYPE: '${{ matrix.type }}' TFLAGS: '${{ matrix.tflags }}' run: | @@ -514,7 +527,7 @@ jobs: if: ${{ matrix.tflags != 'skipall' && matrix.tflags != 'skiprun' }} timeout-minutes: ${{ contains(matrix.tflags, '-t') && 15 || 10 }} env: - MATRIX_ENV: '${{ matrix.env }}' + MATRIX_CONFIG: '${{ matrix.config }}' MATRIX_INSTALL: '${{ matrix.install }}' TFLAGS: '${{ matrix.tflags }}' run: | @@ -527,14 +540,14 @@ jobs: unset CURL_TEST_SSH_KEYALGO # libssh2 built with WinCNG does not support ssh-ed25519 hostkeys export CURL_TEST_SSH_ENABLE_KEX=diffie-hellman-group-exchange-sha256 fi - if [[ "${MATRIX_INSTALL} " = *'-libssh '* && \ + if [[ "${MATRIX_CONFIG} " = *'-DCURL_USE_LIBSSH=ON'* && \ "${MATRIX_ENV}" = *'x86_64'* ]]; then export CURL_TEST_SSH_DISABLE_KEX=mlkem768x25519-sha256 # broken with libssh 0.12.0 Windows x64 fi fi if [ -n "${MATRIX_OPENSSH}" ]; then # OpenSSH-Windows TFLAGS+=' ~601 ~603 ~617 ~619 ~621 ~641 ~665 ~2004 ~3022' # SCP - if [[ "${MATRIX_INSTALL} " = *'libssh '* ]]; then + if [[ "${MATRIX_CONFIG} " = *'-DCURL_USE_LIBSSH=ON'* ]]; then TFLAGS+=' ~614' # 'SFTP pre-quote chmod' SFTP, pre-quote, directory fi fi From 8049695ddd678267b2ea2a45489fb7a71e6fc891 Mon Sep 17 00:00:00 2001 From: Viktor Szakats Date: Wed, 30 Sep 2026 15:10:38 +0200 Subject: [PATCH 325/481] GHA/windows: reuse pre-installed MSYS2 in MSVC arm64 job To inherit MSYS2 environment, which is now pre-installed on Windows ARM64 runners. Saving ~30s in the MSYS2 install step. Ref: https://github.com/actions/runner-images/blob/main/images/windows/Windows11-Arm64-Readme.md Follow-up to be718daf995b620dd8bf89d725d1c4c031b3840a #17067 Follow-up to 27d24690a46138a949a555da3d5487e98fa7f5e9 #16429 Closes #23190 --- .github/workflows/windows.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 52ef56a5d866..f28f46b586e9 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -1013,7 +1013,7 @@ jobs: - uses: msys2/setup-msys2@ec48f7c5447b3140e2b088413ae3a55687bccb6e # v2.33.0 with: msystem: ${{ matrix.arch == 'arm64' && 'clangarm64' || 'ucrt64' }} - release: ${{ contains(matrix.image, 'arm') }} + release: false cache: ${{ contains(matrix.image, 'arm') }} path-type: inherit install: >- From 410e444495c9e95ca56af15d879df27d1358f572 Mon Sep 17 00:00:00 2001 From: Viktor Szakats Date: Wed, 30 Sep 2026 16:15:16 +0200 Subject: [PATCH 326/481] GHA: drop `actionlint`, abandoned upstream No release in 6 months, no commit in 5 months. Lots of debt and a matching number of local workarounds. Ref: https://github.com/rhysd/actionlint Follow-up to 60dd72b1be7b06110922f5f0690de0b88592667c #19373 Closes #23194 --- .github/workflows/checksrc.yml | 23 ++--------------------- .github/workflows/macos.yml | 2 -- .github/workflows/windows.yml | 1 - 3 files changed, 2 insertions(+), 24 deletions(-) diff --git a/.github/workflows/checksrc.yml b/.github/workflows/checksrc.yml index c5c7dfc960d1..b9035b933fde 100644 --- a/.github/workflows/checksrc.yml +++ b/.github/workflows/checksrc.yml @@ -195,13 +195,13 @@ jobs: timeout-minutes: 2 uses: $/.github/actions/pkg-install with: - brew: actionlint shellcheck zizmor + brew: shellcheck zizmor - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false - - name: 'zizmor/actionlint (prepare)' + - name: 'zizmor (prepare)' # Replace custom bash-like shell designators with the standard one to make linters process them run: sed -i.bak -E 's/shell\x3a .+ zizmor\x3a ignore.+$/shell\x3a bash/g' .github/workflows/*.yml @@ -221,25 +221,6 @@ jobs: eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)" git ls-files '.github/actions/**/*.yml' '.github/dependabot.yml' '.github/workflows/*.yml' -z | xargs -0 -r zizmor --persona auditor -- || true - - name: 'actionlint' - background: true - run: | - eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)" - export SHELLCHECK_OPTS='--exclude=1090,1091,2086,2153 --enable=avoid-nullary-conditions,deprecate-which' - actionlint --version - actionlint \ - --ignore matrix \ - --ignore ubuntu-26.04 \ - --ignore ubuntu-24.04-firewall \ - --ignore xcode-27 \ - --ignore 'unexpected key "background"' \ - --ignore 'unexpected key "cache-mode"' \ - --ignore 'unexpected key "wait"' \ - --ignore 'unexpected key "wait-all"' \ - --ignore 'must run script with "run" section' \ - --ignore 'invalid format because ref is missing' \ - .github/workflows/*.yml - - name: 'shellcheck CI' background: true run: | diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index d76fac8a4506..444987c3111a 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -79,7 +79,6 @@ jobs: install_steps: libressl # FIXME: Could not make OPENSSL_ROOT_DIR work. CMake seems to prepend sysroot to it. generator: Xcode - xcode: '' # default Xcode. Set it once to silence actionlint. options: --config Debug generate: >- -DCMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED=OFF @@ -235,7 +234,6 @@ jobs: compiler: gcc-13 configure: --without-ssl --with-brotli --with-zstd --with-apple-idn tflags: '--min=1620' - xcode: '' # default Xcode. Set it once to silence actionlint. - name: '!ssl libssh2 AppleIDN' compiler: clang diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index f28f46b586e9..987e983fb367 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -651,7 +651,6 @@ jobs: config: '-DENABLE_DEBUG=ON -DBUILD_SHARED_LIBS=OFF -DCURL_USE_SCHANNEL=ON -DCURL_TARGET_WINDOWS_VERSION=0x0600' type: 'Debug' tflags: 'skipall' - chkprefill: '' # Set it once to silence actionlint fail-fast: false steps: - name: 'preinstalled tools' From 3ae15e6f699a7a0060f158b3e88a87dd10bca547 Mon Sep 17 00:00:00 2001 From: Viktor Szakats Date: Wed, 30 Sep 2026 15:39:30 +0200 Subject: [PATCH 327/481] GHA/windows: enable MSYS2 cache for MSVC Intel Win32 job Cost is one new cache entry, 26MB. Benefit is one MSYS2 init step faster by ~10 seconds. It was already enabled for the ARM64 Win32 job. Update the logic to enable caching for jobs running tests, which need Perl and Perl benefits from caching. (Something to review when introducing new MSVC jobs.) Closes #23191 --- .github/workflows/windows.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 987e983fb367..a15c85e9d390 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -1013,7 +1013,7 @@ jobs: with: msystem: ${{ matrix.arch == 'arm64' && 'clangarm64' || 'ucrt64' }} release: false - cache: ${{ contains(matrix.image, 'arm') }} + cache: ${{ matrix.tflags != 'skiprun' }} path-type: inherit install: >- mingw-w64-${{ matrix.env }}-libpsl From 0b0063174bc6a42ce027931ab855757f4acdd9b2 Mon Sep 17 00:00:00 2001 From: Viktor Szakats Date: Wed, 30 Sep 2026 18:41:52 +0200 Subject: [PATCH 328/481] GHA/linux: split long valgrind job into two To match rest of valgrind jobs, and to make a CI cycle finish faster. Also: drop duplicate `--enable-unity` option. Follow-up to 46d0ade0866f591289778cf1b2cb9945f61418a4 #21099 Closes #23195 --- .github/workflows/linux.yml | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index e65034d2829d..585a9c9248f2 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -284,14 +284,17 @@ jobs: image: ubuntu-24.04-arm configure: --with-openssl --disable-ipv6 --enable-debug --disable-unity --disable-libcurl-option --disable-digest-auth --enable-ntlm - - name: 'curl_global_init_mem debug valgrind' + - name: 'curl_global_init_mem debug valgrind 1' image: ubuntu-24.04-arm install_packages: valgrind - tflags: '--min=1800' - configure: >- - --enable-init-mem-debug - --with-openssl --disable-debug --enable-unity - --enable-ntlm + tflags: '--subset=0/2' + configure: --enable-init-mem-debug --with-openssl --disable-debug --enable-ntlm + + - name: 'curl_global_init_mem debug valgrind 2' + image: ubuntu-24.04-arm + install_packages: valgrind + tflags: '--subset=1/2' + configure: --enable-init-mem-debug --with-openssl --disable-debug --enable-ntlm - name: 'openssl https-only' image: ubuntu-24.04-arm From 7de1340cb2c6365d70a9bb81f4d083339c711cf7 Mon Sep 17 00:00:00 2001 From: Viktor Szakats Date: Tue, 1 Sep 2026 00:39:29 +0200 Subject: [PATCH 329/481] tidy-up: miscellaneous - autotools: sync LibreSSL detection with rest of forks. - CMakeLists.txt: say `time_t` sign is pre-fill (for amiga/dos), auto-detection also works now. Follow-up to 2f1a21b1d9dd078e6a375317fdcf7b3593f2424f #22849 - urldata.h: drop redundant parentheses from macro values. - curl_setup.h, libssh.c: fix indent. - GHA/windows: update comment. - GHA/windows: update Cygwin package search URL. - GHA/linux: drop curl build option from LibreSSL configure line. - GHA: sync option order between workflows. - docs/INSTALL.md: mention missing AWS-LC in comment. - test804: add newline for readability. - OS400: clang-format. - docs, lib: drop spaces after cast. Credits-to: Patrick Monnerat Ref: #22729 - tests/memanalyzer.pm: move literal to right-side of comparison. - drop `v` prefix from version numbers in docs, where missing. - minor whitespace fixes. Closes #23146 --- .github/workflows/http3-linux.yml | 2 +- .github/workflows/linux.yml | 6 +-- .github/workflows/macos.yml | 7 +--- .github/workflows/windows.yml | 6 +-- CMakeLists.txt | 6 +-- docs/HTTP3.md | 10 ++--- docs/INSTALL.md | 4 +- docs/RUSTLS.md | 2 +- docs/internals/CLIENT-READERS.md | 2 +- docs/internals/CLIENT-WRITERS.md | 2 +- docs/internals/CONNECTION-FILTERS.md | 2 +- docs/libcurl/curl_mime_data_cb.md | 2 +- lib/api.c | 6 +-- lib/curl_setup.h | 2 +- lib/urldata.h | 4 +- lib/vssh/libssh.c | 2 +- lib/vtls/rustls.c | 4 +- projects/OS400/ccsidcurl.c | 54 +++++++++++++-------------- projects/OS400/ccsidcurl.h | 5 +-- projects/OS400/curlcl.c | 6 +-- projects/OS400/curlmain.c | 16 ++++---- projects/OS400/os400sys.c | 4 +- projects/vms/build_vms.com | 2 +- projects/vms/curl_gnv_build_steps.txt | 2 +- projects/vms/readme | 4 +- src/tool_parsecfg.c | 2 +- tests/data/test804 | 3 +- tests/memanalyzer.pm | 2 +- tests/runner.pm | 2 +- tests/test1173.pl | 2 +- 30 files changed, 84 insertions(+), 89 deletions(-) diff --git a/.github/workflows/http3-linux.yml b/.github/workflows/http3-linux.yml index 4446b45f0efe..16cbb57f84cc 100644 --- a/.github/workflows/http3-linux.yml +++ b/.github/workflows/http3-linux.yml @@ -241,7 +241,7 @@ jobs: --location --proto-redir =https "https://github.com/awslabs/aws-lc/archive/refs/tags/v${AWSLC_VERSION}.tar.gz" --output pkg.bin sha256sum pkg.bin && tar -xzf pkg.bin && rm -f pkg.bin cd "aws-lc-${AWSLC_VERSION}" - cmake -B . -G Ninja -DBUILD_SHARED_LIBS=ON -DBUILD_TOOL=OFF -DBUILD_TESTING=OFF -DCMAKE_INSTALL_PREFIX=/home/runner/awslc/build + cmake -B . -G Ninja -DBUILD_SHARED_LIBS=ON -DBUILD_TESTING=OFF -DBUILD_TOOL=OFF -DCMAKE_INSTALL_PREFIX=/home/runner/awslc/build cmake --build . cmake --install . diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 585a9c9248f2..eb8a695654d1 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -539,7 +539,7 @@ jobs: "https://ftp.openbsd.org/pub/OpenBSD/LibreSSL/libressl-${LIBRESSL_VERSION}.tar.gz" --output pkg.bin sha256sum pkg.bin && tar -xzf pkg.bin && rm -f pkg.bin cd "libressl-${LIBRESSL_VERSION}" - cmake -B . -G Ninja -DLIBRESSL_APPS=OFF -DLIBRESSL_TESTS=OFF -DCMAKE_INSTALL_PREFIX=/home/runner/libressl -DCURL_ENABLE_NTLM=ON + cmake -B . -G Ninja -DLIBRESSL_APPS=OFF -DLIBRESSL_TESTS=OFF -DCMAKE_INSTALL_PREFIX=/home/runner/libressl cmake --build . cmake --install . @@ -786,7 +786,7 @@ jobs: --location --proto-redir =https "https://github.com/awslabs/aws-lc/archive/refs/tags/v${AWSLC_VERSION}.tar.gz" --output pkg.bin sha256sum pkg.bin && tar -xzf pkg.bin && rm -f pkg.bin cd "aws-lc-${AWSLC_VERSION}" - cmake -B . -G Ninja -DCMAKE_INSTALL_PREFIX=/home/runner/awslc -DBUILD_TOOL=OFF -DBUILD_TESTING=OFF + cmake -B . -G Ninja -DBUILD_TESTING=OFF -DBUILD_TOOL=OFF -DCMAKE_INSTALL_PREFIX=/home/runner/awslc cmake --build . cmake --install . @@ -807,7 +807,7 @@ jobs: --location --proto-redir =https "https://github.com/google/boringssl/releases/download/${BORINGSSL_VERSION}/boringssl-${BORINGSSL_VERSION}.tar.gz" --output pkg.bin sha256sum pkg.bin && tar -xzf pkg.bin && rm -f pkg.bin cd "boringssl-${BORINGSSL_VERSION}" - cmake -B . -G Ninja -DCMAKE_INSTALL_PREFIX=/home/runner/boringssl -DBUILD_TESTING=OFF + cmake -B . -G Ninja -DBUILD_TESTING=OFF -DCMAKE_INSTALL_PREFIX=/home/runner/boringssl cmake --build . cmake --install . diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index 444987c3111a..eb68da83fffb 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -124,13 +124,10 @@ jobs: --location --proto-redir =https "https://github.com/libressl/portable/releases/download/v${LIBRESSL_VERSION}/libressl-${LIBRESSL_VERSION}.tar.gz" --output pkg.bin sha256sum pkg.bin && tar -xzf pkg.bin && rm -f pkg.bin cd "libressl-${LIBRESSL_VERSION}" - cmake -B . -G Ninja \ - -DCMAKE_INSTALL_PREFIX=/Users/runner/libressl \ + cmake -B . -G Ninja -DLIBRESSL_APPS=OFF -DLIBRESSL_TESTS=OFF -DCMAKE_INSTALL_PREFIX=/Users/runner/libressl \ -DCMAKE_SYSTEM_NAME=iOS \ -DCMAKE_SYSTEM_PROCESSOR=aarch64 \ - -DBUILD_SHARED_LIBS=OFF \ - -DLIBRESSL_APPS=OFF \ - -DLIBRESSL_TESTS=OFF + -DBUILD_SHARED_LIBS=OFF cmake --build . cmake --install . --verbose diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index a15c85e9d390..162bf6f629bd 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -106,7 +106,7 @@ jobs: # https://cygwin.com/mirrors.html # Main mirror status: https://archlinux.org/mirrors/kernel.org/ site: https://mirrors.kernel.org/sourceware/cygwin/ - # https://cygwin.com/cgi-bin2/package-grep.cgi + # https://cygwin.com/packages/ packages: >- ${{ matrix.build == 'autotools' && 'autoconf automake libtool make' || 'cmake ninja' }} gcc-core binutils perl pkgconf @@ -407,7 +407,7 @@ jobs: [ "${TFLAGS}" = 'skiprun' ] && options+=' -D_CURL_SKIP_BUILD_CERTS=ON' [ "${_chkprefill}" = '_chkprefill' ] && options+=' -D_CURL_PREFILL=OFF' cmake -B "bld${_chkprefill}" -G Ninja ${options} \ - -DCMAKE_INSTALL_PREFIX="${HOME}"/curl-install \ + -DCMAKE_INSTALL_PREFIX="$HOME"/curl-install \ -DCMAKE_C_FLAGS="${CFLAGS_CMAKE} ${CPPFLAGS}" \ -DCMAKE_BUILD_TYPE="${MATRIX_TYPE}" \ -DCMAKE_UNITY_BUILD=ON -DCMAKE_UNITY_BUILD_BATCH_SIZE=30 \ @@ -837,7 +837,7 @@ jobs: options+=' -DENABLE_UNICODE=ON -DUSE_SSLS_EXPORT=ON' options+=' -DCMAKE_C_COMPILER=clang-22' options+=" -DCMAKE_RC_COMPILER=llvm-windres-$(clang-22 -dumpversion | cut -d '.' -f 1)" - options+=' -DCMAKE_RC_FLAGS=--target=pe-x86-64' # Required on ubuntu-26.04-arm + options+=' -DCMAKE_RC_FLAGS=--target=pe-x86-64' # Required for non-x86-64 runners else options+=" -DCMAKE_C_COMPILER=${TRIPLET}-gcc" fi diff --git a/CMakeLists.txt b/CMakeLists.txt index f595e973a78b..c0d5724ec407 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -615,13 +615,13 @@ elseif(APPLE) set(HAVE_WRITABLE_ARGV 1) set(HAVE_SENDMMSG 0) endif() +if(DOS OR AMIGA) + set(HAVE_TIME_T_UNSIGNED 1) # Pre-fill +endif() if(AMIGA) set(HAVE_GETADDRINFO 0) # Breaks the build when detected and used. endif() -if(DOS OR AMIGA) - set(HAVE_TIME_T_UNSIGNED 1) -endif() if(NOT WIN32) find_package(Threads) diff --git a/docs/HTTP3.md b/docs/HTTP3.md index a7cc774d765c..c913aebd2457 100644 --- a/docs/HTTP3.md +++ b/docs/HTTP3.md @@ -40,7 +40,7 @@ Building curl with ngtcp2 involves 3 components: `ngtcp2` itself, `nghttp3` and a QUIC supporting TLS library. The supported TLS libraries are covered below. -While any version of `ngtcp2` and `nghttp3` from v1.0.0 on are expected to +While any version of `ngtcp2` and `nghttp3` from 1.0.0 on are expected to work, using the latest versions often brings functional and performance improvements. @@ -49,11 +49,11 @@ placeholders for the version you build. ## Build with OpenSSL or fork -OpenSSL v3.5.0+ requires *ngtcp2* v1.12.0+. Earlier versions do not work. +OpenSSL 3.5.0+ requires *ngtcp2* 1.12.0+. Earlier versions do not work. -Build OpenSSL (v3.5.0+) or fork AWS-LC, BoringSSL, LibreSSL or quictls: +Build OpenSSL (3.5.0+) or fork AWS-LC, BoringSSL, LibreSSL or quictls: - # Instructions for OpenSSL v3.5.0+ + # Instructions for OpenSSL 3.5.0+ % git clone --depth 1 --branch openssl-$OPENSSL_VERSION https://github.com/openssl/openssl % cd openssl % ./config --prefix=/path/to/openssl --libdir=lib @@ -215,7 +215,7 @@ but in case of problems, we recommend their latest release tag. ## Build -Build quiche and BoringSSL (described here for quiche v0.30.0, the locations +Build quiche and BoringSSL (described here for quiche 0.30.0, the locations where BoringSSL is to be found vary with version): % git clone --depth 1 --branch 0.30.0 --recursive https://github.com/cloudflare/quiche diff --git a/docs/INSTALL.md b/docs/INSTALL.md index ce1b63040555..56adcbb42701 100644 --- a/docs/INSTALL.md +++ b/docs/INSTALL.md @@ -504,8 +504,8 @@ install `libssl.a` and `libcrypto.a` to `$TOOLCHAIN/sysroot/usr/lib` and copy for Android using OpenSSL like this: ```sh -# For BoringSSL/OpenSSL. In general, you need to the SSL/TLS layer's transitive -# dependencies if you are linking statically. +# For AWS-LC/BoringSSL/OpenSSL. In general, you need to the SSL/TLS layer's +# transitive dependencies if you are linking statically. LIBS='-lssl -lcrypto -lc++' ./configure --host aarch64-linux-android --with-pic --disable-shared --with-openssl="$TOOLCHAIN/sysroot/usr" ``` diff --git a/docs/RUSTLS.md b/docs/RUSTLS.md index ed032f7fb628..4899a10aacef 100644 --- a/docs/RUSTLS.md +++ b/docs/RUSTLS.md @@ -9,7 +9,7 @@ SPDX-License-Identifier: curl [Rustls is a TLS backend written in Rust](https://docs.rs/rustls/). curl can be built to use it as an alternative to OpenSSL or other TLS backends. We use the [rustls-ffi C bindings](https://github.com/rustls/rustls-ffi). This -version of curl is compatible with `rustls-ffi` v0.15.x. +version of curl is compatible with `rustls-ffi` 0.15.x. ## Getting rustls-ffi diff --git a/docs/internals/CLIENT-READERS.md b/docs/internals/CLIENT-READERS.md index 11cf86da6857..8b1040698822 100644 --- a/docs/internals/CLIENT-READERS.md +++ b/docs/internals/CLIENT-READERS.md @@ -7,7 +7,7 @@ SPDX-License-Identifier: curl # curl client readers Client readers is a design in the internals of libcurl, not visible in its -public API. They were started in curl v8.7.0. This document describes +public API. They were started in curl 8.7.0. This document describes the concepts, its high level implementation and the motivations. ## Naming diff --git a/docs/internals/CLIENT-WRITERS.md b/docs/internals/CLIENT-WRITERS.md index e00cd263be63..4e28d188ab0e 100644 --- a/docs/internals/CLIENT-WRITERS.md +++ b/docs/internals/CLIENT-WRITERS.md @@ -7,7 +7,7 @@ SPDX-License-Identifier: curl # curl client writers Client writers is a design in the internals of libcurl, not visible in its -public API. They were started in curl v8.5.0. This document describes the +public API. They were started in curl 8.5.0. This document describes the concepts, its high level implementation and the motivations. ## Naming diff --git a/docs/internals/CONNECTION-FILTERS.md b/docs/internals/CONNECTION-FILTERS.md index 1a817a15672a..515624a9fad5 100644 --- a/docs/internals/CONNECTION-FILTERS.md +++ b/docs/internals/CONNECTION-FILTERS.md @@ -7,7 +7,7 @@ SPDX-License-Identifier: curl # curl connection filters Connection filters is a design in the internals of curl, not visible in its -public API. They were added in curl v7.87.0. This document describes the +public API. They were added in curl 7.87.0. This document describes the concepts, its high level implementation and the motivations. ## Filters diff --git a/docs/libcurl/curl_mime_data_cb.md b/docs/libcurl/curl_mime_data_cb.md index d5965c86c769..ba1a34ac6804 100644 --- a/docs/libcurl/curl_mime_data_cb.md +++ b/docs/libcurl/curl_mime_data_cb.md @@ -135,7 +135,7 @@ static size_t read_callback(char *buffer, size_t size, size_t nitems, static int seek_callback(void *arg, curl_off_t offset, int origin) { - struct ctl *p = (struct ctl *) arg; + struct ctl *p = (struct ctl *)arg; switch(origin) { case SEEK_END: diff --git a/lib/api.c b/lib/api.c index acf372cec802..e46a6b738e3f 100644 --- a/lib/api.c +++ b/lib/api.c @@ -235,7 +235,7 @@ bool Curl_eapi_enter(struct Curl_eapi_guard *guard, #ifdef CURLVERBOSE DEBUGF(curl_mfprintf(stderr, "EAPI guard: calling %hu with call to %u ongoing\n", (uint16_t)fn, - data->callstack.calls[data->callstack.count-1])); + data->callstack.calls[data->callstack.count - 1])); #endif result = CURLE_RECURSIVE_API_CALL; goto out; @@ -246,7 +246,7 @@ bool Curl_eapi_enter(struct Curl_eapi_guard *guard, DEBUGF(curl_mfprintf(stderr, "EAPI guard: calling %hu with multi call to %u ongoing\n", (uint16_t)fn, - data->multi->callstack.calls[data->multi->callstack.count-1])); + data->multi->callstack.calls[data->multi->callstack.count - 1])); #endif result = CURLE_RECURSIVE_API_CALL; goto out; @@ -359,7 +359,7 @@ bool Curl_mapi_enter(struct Curl_mapi_guard *guard, #ifdef CURLVERBOSE DEBUGF(curl_mfprintf(stderr, "MAPI guard: calling %hu with call to %u ongoing\n", (uint16_t)fn, - multi->callstack.calls[multi->callstack.count-1])); + multi->callstack.calls[multi->callstack.count - 1])); #endif mresult = CURLM_RECURSIVE_API_CALL; goto out; diff --git a/lib/curl_setup.h b/lib/curl_setup.h index 758948286d2b..41587c7f4768 100644 --- a/lib/curl_setup.h +++ b/lib/curl_setup.h @@ -1078,7 +1078,7 @@ typedef unsigned int curl_bit; #define SOCKEISCONN WSAEISCONN #define SOCKEMSGSIZE WSAEMSGSIZE /* Use literal value to work around clang-tidy <=20 misreporting - 'readability-uppercase-literal-suffix' with mingw-w64 headers */ + 'readability-uppercase-literal-suffix' with mingw-w64 headers */ #define SOCKENOMEM 8L /* WSA_NOT_ENOUGH_MEMORY */ #define SOCKETIMEDOUT WSAETIMEDOUT #define SOCKEWOULDBLOCK WSAEWOULDBLOCK diff --git a/lib/urldata.h b/lib/urldata.h index 85c5b6badb0a..ed4b27412877 100644 --- a/lib/urldata.h +++ b/lib/urldata.h @@ -1241,9 +1241,9 @@ struct Curl_easy { #define CURL_EASY_STR(d, id) \ Curl_u8_strset_get(&(d)->set.strings, (uint8_t)(id)) #define CURL_EASY_STR_SET(d, id, s, slen) \ - Curl_u8_strset_setx(&(d)->set.strings, (uint8_t)(id), (s), (slen)) + Curl_u8_strset_setx(&(d)->set.strings, (uint8_t)(id), s, slen) #define CURL_EASY_STR_SETN(d, id, s) \ - Curl_u8_strset_setn(&(d)->set.strings, (uint8_t)(id), (s)) + Curl_u8_strset_setn(&(d)->set.strings, (uint8_t)(id), s) #define CURL_EASY_STR_CLEAR(d, id) \ Curl_u8_strset_unset(&(d)->set.strings, (uint8_t)(id)) #define CURL_EASY_STR_CLEAR0(d, id) \ diff --git a/lib/vssh/libssh.c b/lib/vssh/libssh.c index 6516933cf427..c354ca55245b 100644 --- a/lib/vssh/libssh.c +++ b/lib/vssh/libssh.c @@ -834,7 +834,7 @@ static int myssh_in_AUTHLIST(struct Curl_easy *data, /* For public key auth we need either the private key or CURLSSH_AUTH_AGENT. */ if((sshc->auth_methods & SSH_AUTH_METHOD_PUBLICKEY) && - ((sshc->priv_key && sshc->priv_key[0]) || + ((sshc->priv_key && sshc->priv_key[0]) || (data->set.ssh_auth_types & CURLSSH_AUTH_AGENT))) { myssh_to(data, sshc, SSH_AUTH_PKEY_INIT); infof(data, "Authentication using SSH public key file"); diff --git a/lib/vtls/rustls.c b/lib/vtls/rustls.c index ac2c3a9127b1..3373b703bc08 100644 --- a/lib/vtls/rustls.c +++ b/lib/vtls/rustls.c @@ -1222,9 +1222,9 @@ static CURLcode cr_connect(struct Curl_cfilter *cf, struct Curl_easy *data, "rustls: handshake complete, %s, ciphersuite: %.*s, " "key exchange group: %.*s", ver, - (int) ciphersuite_name.len, + (int)ciphersuite_name.len, ciphersuite_name.data, - (int) kex_group_name.len, + (int)kex_group_name.len, kex_group_name.data); } if(data->set.ssl.certinfo) { diff --git a/projects/OS400/ccsidcurl.c b/projects/OS400/ccsidcurl.c index 8b3b2fe6c6a0..02ead8934c04 100644 --- a/projects/OS400/ccsidcurl.c +++ b/projects/OS400/ccsidcurl.c @@ -48,7 +48,7 @@ #include "os400sys.h" #ifndef SIZE_MAX -#define SIZE_MAX ((size_t) ~0) +#define SIZE_MAX ((size_t)~0) #endif #define ASCII_CCSID 819 /* Use ISO-8859-1 as ASCII. */ @@ -58,14 +58,12 @@ #define ALLOC_GRANULE 8 /* Alloc. granule for curl_formadd_ccsid(). */ - /* A string terminator that works for all CCSIDs. */ -static const char universal_terminator[] = {0, 0, 0, 0}; +static const char universal_terminator[] = { 0, 0, 0, 0 }; /* Freeing const pointers more easily. */ #define untyped_free(p) curlx_free(CURL_UNCONST(p)) - static void makeOS400IconvCode(char buf[ICONV_ID_SIZE], unsigned int ccsid) { /* @@ -144,7 +142,7 @@ static int convert(char *d, size_t dlen, const char *s, size_t slen, i = dlen; - if((int)iconv(cd, (char **) &s, &lslen, &d, &dlen) < 0) + if((int)iconv(cd, (char **)&s, &lslen, &d, &dlen) < 0) i = -1; else i -= dlen; @@ -181,9 +179,9 @@ static CURLcode dyn_addn_CCSID(struct dynbuf *db, int err = 0; dlen = sizeof(buffer); - if((int)iconv(cd, (char **) &mem, - len == CURL_ZERO_TERMINATED ? &dummylen : &len, - &dptr, &dlen) < 0) { + if((int)iconv(cd, (char **)&mem, + len == CURL_ZERO_TERMINATED ? &dummylen : &len, &dptr, + &dlen) < 0) { /* !checksrc! disable ERRNOVAR 1 */ err = errno; } @@ -218,7 +216,7 @@ static CURLcode dyn_addn_CCSID(struct dynbuf *db, /* The null terminator has been converted AND counted as a character. * Measure it by an additional conversion and drop it. */ int tlen = convert(buffer, sizeof(buffer), universal_terminator, 1, - ASCII_CCSID, ccsidout); + ASCII_CCSID, ccsidout); if(tlen < 0 || tlen > dlen) { curlx_dyn_free(db); @@ -421,7 +419,7 @@ time_t curl_getdate_ccsid(const char *p, const time_t *unused, s = curl_from_ccsid(p, ccsid); if(!s) - return (time_t) -1; + return (time_t)-1; t = curl_getdate(s, unused); untyped_free(s); @@ -842,7 +840,7 @@ CURLFORMcode curl_formadd_ccsid(struct curl_httppost **httppost, switch(option) { case CURLFORM_END: - forms = NULL; /* Leave array mode. */ + forms = NULL; /* Leave array mode. */ continue; case CURLFORM_ARRAY: @@ -1022,8 +1020,8 @@ CURLFORMcode curl_formadd_ccsid(struct curl_httppost **httppost, struct cfcdata { curl_formget_callback append; - void * arg; - unsigned int ccsid; + void *arg; + unsigned int ccsid; }; static size_t formget_callback_ccsid(void *arg, const char *buf, size_t len) @@ -1041,7 +1039,7 @@ static size_t formget_callback_ccsid(void *arg, const char *buf, size_t len) curlx_dyn_init(&db, MAX_CONV_EXPANSION * CURL_MAX_INPUT_LENGTH); if(dyn_addn_CCSID(&db, buf, len, ASCII_CCSID, p->ccsid)) - return (size_t) -1; + return (size_t)-1; olen = curlx_dyn_len(&db); ret = p->append(p->arg, curlx_dyn_ptr(&db), olen); @@ -1057,7 +1055,7 @@ int curl_formget_ccsid(struct curl_httppost *form, void *arg, lcfc.append = append; lcfc.arg = arg; lcfc.ccsid = ccsid; - return curl_formget(form, (void *) &lcfc, formget_callback_ccsid); + return curl_formget(form, (void *)&lcfc, formget_callback_ccsid); } CURLcode curl_easy_setopt_ccsid(CURL *curl, CURLoption tag, ...) @@ -1174,7 +1172,7 @@ CURLcode curl_easy_setopt_ccsid(CURL *curl, CURLoption tag, ...) case CURLOPT_USERNAME: case CURLOPT_USERPWD: case CURLOPT_XOAUTH2_BEARER: - /* END TRANSLATABLE STRING OPTIONS */ + /* END TRANSLATABLE STRING OPTIONS */ s = va_arg(arg, const char *); ccsid = va_arg(arg, unsigned int); @@ -1225,7 +1223,7 @@ CURLcode curl_easy_setopt_ccsid(CURL *curl, CURLoption tag, ...) copying them. */ result = curl_easy_setopt(curl, CURLOPT_POSTFIELDS, s); if(!result) { - data->set.str_copypostfields = CURL_UNCONST(s); /* Adopt. */ + data->set.str_copypostfields = CURL_UNCONST(s); /* Adopt. */ if(pfsize != -1) data->set.postfieldsize = len; } @@ -1243,9 +1241,9 @@ CURLcode curl_easy_setopt_ccsid(CURL *curl, CURLoption tag, ...) if(bp && bp->data && bp->len && ccsid != NOCONV_CCSID && ccsid != ASCII_CCSID) { - result = dyn_addn_CCSID(&db, bp->data, bp->len, ccsid, ASCII_CCSID); - if(result) - break; + result = dyn_addn_CCSID(&db, bp->data, bp->len, ccsid, ASCII_CCSID); + if(result) + break; blob.data = curlx_dyn_ptr(&db); blob.len = curlx_dyn_len(&db); @@ -1326,9 +1324,10 @@ char *curl_pushheader_byname_ccsid(struct curl_pushheaders *h, return CURL_UNCONST(d); } -static CURLcode -mime_string_call(curl_mimepart *part, const char *string, unsigned int ccsid, - CURLcode (*mimefunc)(curl_mimepart *part, const char *string)) +static CURLcode mime_string_call(curl_mimepart *part, const char *string, + unsigned int ccsid, + CURLcode (*mimefunc)(curl_mimepart *part, + const char *string)) { const char *s; CURLcode result; @@ -1442,8 +1441,8 @@ CURLUcode curl_url_set_ccsid(CURLU *handle, CURLUPart what, const char *part, return result; } -const struct curl_easyoption * -curl_easy_option_by_name_ccsid(const char *name, unsigned int ccsid) +const struct curl_easyoption *curl_easy_option_by_name_ccsid( + const char *name, unsigned int ccsid) { const struct curl_easyoption *option = NULL; @@ -1460,9 +1459,8 @@ curl_easy_option_by_name_ccsid(const char *name, unsigned int ccsid) } /* Return option name in the given ccsid. */ -const char * -curl_easy_option_get_name_ccsid(const struct curl_easyoption *option, - unsigned int ccsid) +const char *curl_easy_option_get_name_ccsid( + const struct curl_easyoption *option, unsigned int ccsid) { const char *name = NULL; diff --git a/projects/OS400/ccsidcurl.h b/projects/OS400/ccsidcurl.h index f38e57967c2c..43312fd8adc2 100644 --- a/projects/OS400/ccsidcurl.h +++ b/projects/OS400/ccsidcurl.h @@ -92,10 +92,9 @@ CURL_EXTERN CURLUcode curl_url_set_ccsid(CURLU *handle, CURLUPart what, const char *part, unsigned int flags, unsigned int ccsid); CURL_EXTERN const struct curl_easyoption *curl_easy_option_by_name_ccsid( - const char *name, unsigned int ccsid); + const char *name, unsigned int ccsid); CURL_EXTERN const char *curl_easy_option_get_name_ccsid( - const struct curl_easyoption *option, - unsigned int ccsid); + const struct curl_easyoption *option, unsigned int ccsid); CURL_EXTERN const char *curl_url_strerror_ccsid(CURLUcode error, unsigned int ccsid); CURL_EXTERN CURLHcode curl_easy_header_ccsid(CURL *easy, const char *name, diff --git a/projects/OS400/curlcl.c b/projects/OS400/curlcl.c index 8085307f31ca..76bf873b296a 100644 --- a/projects/OS400/curlcl.c +++ b/projects/OS400/curlcl.c @@ -39,13 +39,13 @@ /* Variable-length string, with 16-bit length. */ struct vary2 { short len; - char string[5000]; + char string[5000]; }; /* Arguments from CL command. */ struct arguments { - char *pgm; /* Program name. */ - struct vary2 *cmdargs; /* Command line arguments. */ + char *pgm; /* Program name. */ + struct vary2 *cmdargs; /* Command line arguments. */ }; static int is_ifs(char c) diff --git a/projects/OS400/curlmain.c b/projects/OS400/curlmain.c index fa2e93d35f47..254b66ea382c 100644 --- a/projects/OS400/curlmain.c +++ b/projects/OS400/curlmain.c @@ -36,17 +36,17 @@ #include /* Do not use qadrt.h since it defines unneeded static procedures. */ -extern void QadrtInit(void); -extern int QadrtFreeConversionTable(void); -extern int QadrtFreeEnviron(void); -extern char * setlocale_a(int, const char *); +extern void QadrtInit(void); +extern int QadrtFreeConversionTable(void); +extern int QadrtFreeEnviron(void); +extern char *setlocale_a(int, const char *); /* The ASCII main program. */ -extern int main_a(int argc, char *argv[]); +extern int main_a(int argc, char *argv[]); /* Global values of original EBCDIC arguments. */ -int ebcdic_argc; -char ** ebcdic_argv; +int ebcdic_argc; +char **ebcdic_argv; int main(int argc, char *argv[]) { @@ -61,7 +61,7 @@ int main(int argc, char *argv[]) char dummybuf[128]; /* To/From codes are 32-byte strings with reserved fields initialized to ZEROs */ - const char tocode[32] = { "IBMCCSID01208" }; /* Use UTF-8. */ + const char tocode[32] = { "IBMCCSID01208" }; /* Use UTF-8. */ const char fromcode[32] = { "IBMCCSID000000000010" }; ebcdic_argc = argc; diff --git a/projects/OS400/os400sys.c b/projects/OS400/os400sys.c index bea84500629d..af97616d5cfb 100644 --- a/projects/OS400/os400sys.c +++ b/projects/OS400/os400sys.c @@ -74,8 +74,8 @@ static char *buffer_undef(localkey_t key, long size); static char *buffer_threaded(localkey_t key, long size); static char *buffer_unthreaded(localkey_t key, long size); -static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; -static pthread_key_t thdkey; +static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; +static pthread_key_t thdkey; static struct buffer_t *locbufs; char *(*Curl_thread_buffer)(localkey_t key, long size) = buffer_undef; diff --git a/projects/vms/build_vms.com b/projects/vms/build_vms.com index d79fdb594079..85f6a110a110 100644 --- a/projects/vms/build_vms.com +++ b/projects/vms/build_vms.com @@ -38,7 +38,7 @@ $! Always link a debug image. $! NOIEEE Do not use IEEE floating point. (Alpha/I64) $! VAX must always use DFLOAT $! NOLARGE Disable large-file support if large file support available. -$! (Non-VAX, VMS >= v7.2.) +$! (Non-VAX, VMS >= 7.2.) $! NOLDAP Disable LDAP support if LDAP is available. $! NOKERBEROS Disable Kerberos support if Kerberos is available. $! LIST Create C compiler listings and linker maps. diff --git a/projects/vms/curl_gnv_build_steps.txt b/projects/vms/curl_gnv_build_steps.txt index 88af34f5802e..edc3bd9003ad 100644 --- a/projects/vms/curl_gnv_build_steps.txt +++ b/projects/vms/curl_gnv_build_steps.txt @@ -20,7 +20,7 @@ Currently building curl using GNV takes longer than building curl via DCL. The GNV procedure actually uses the same configure and makefiles that Unix builds use. -Building curl on OpenVMS using GNV requires GNV v2.1-2 or the updated +Building curl on OpenVMS using GNV requires GNV 2.1-2 or the updated images that are available via anonymous FTP at encompasserve.org in the gnv directory. It also requires the GNV Bash 4.2.45 kit as an update from the same location or from the sourceforge.net GNV project. diff --git a/projects/vms/readme b/projects/vms/readme index 9db0ee38735b..e62104311dfa 100644 --- a/projects/vms/readme +++ b/projects/vms/readme @@ -19,8 +19,8 @@ curl_gnv_build_steps.txt and other useful information. Prerequisites: -OpenVMS v7.0 or later (any platform) -DECC v6.5 or later +OpenVMS 7.0 or later (any platform) +DECC 6.5 or later OpenSSL or HP SSL, if you want SSL support What is Here: diff --git a/src/tool_parsecfg.c b/src/tool_parsecfg.c index e7d3a3f87827..a0b75d185a28 100644 --- a/src/tool_parsecfg.c +++ b/src/tool_parsecfg.c @@ -352,7 +352,7 @@ static bool get_line(FILE *input, struct dynbuf *buf, bool *error) return FALSE; /* error */ } - else if(b[rlen-1] == '\n') { + else if(b[rlen - 1] == '\n') { /* end of the line, drop the newline */ size_t len = curlx_dyn_len(buf); if(len) diff --git a/tests/data/test804 b/tests/data/test804 index ea31b524b4bf..275b448ad3ea 100644 --- a/tests/data/test804 +++ b/tests/data/test804 @@ -27,7 +27,8 @@ imap IMAP selects a mailbox when its case changes -'imap://%HOSTIP:%IMAPPORT/Private/;MAILINDEX=123/;SECTION=1' 'imap://%HOSTIP:%IMAPPORT/private/;MAILINDEX=456/;SECTION=2.3' -u user:secret +'imap://%HOSTIP:%IMAPPORT/Private/;MAILINDEX=123/;SECTION=1' +'imap://%HOSTIP:%IMAPPORT/private/;MAILINDEX=456/;SECTION=2.3' -u user:secret diff --git a/tests/memanalyzer.pm b/tests/memanalyzer.pm index 110b39d24111..c8fe3025ace2 100644 --- a/tests/memanalyzer.pm +++ b/tests/memanalyzer.pm @@ -138,7 +138,7 @@ sub memanalyze { elsif(!exists $sizeataddr{$addr}) { push @res, "FREE ERROR: No memory allocated: $line\n"; } - elsif(-1 == $sizeataddr{$addr}) { + elsif($sizeataddr{$addr} == -1) { push @res, "FREE ERROR: Memory freed twice: $line\n"; push @res, "FREE ERROR: Previously freed at: $getmem{$addr}\n"; } diff --git a/tests/runner.pm b/tests/runner.pm index b3241d68dcc9..f1fa3f6b5efc 100644 --- a/tests/runner.pm +++ b/tests/runner.pm @@ -768,7 +768,7 @@ sub singletest_prepare { my @ldparts = split(/\//, $LOGDIR); my $nparts = @ldparts; my @parts = split(/\//, $path); - if(join("/", @parts[0..$nparts-1]) eq $LOGDIR) { + if(join("/", @parts[0..$nparts - 1]) eq $LOGDIR) { # the file is in $LOGDIR/ my $d = shift @parts; for(@parts) { diff --git a/tests/test1173.pl b/tests/test1173.pl index 14894c625bd0..917bd65ff797 100755 --- a/tests/test1173.pl +++ b/tests/test1173.pl @@ -342,7 +342,7 @@ sub scanmanpage { printf STDERR "$file:%u Got %s, when %s was expected\n", $shline{$finesh}, $finesh, - $order[$shused-1]; + $order[$shused - 1]; $errors++; return; } From 39e585ef6432257169421bb8e5fbc5a5b27bfe1e Mon Sep 17 00:00:00 2001 From: Viktor Szakats Date: Fri, 25 Sep 2026 15:15:01 +0200 Subject: [PATCH 330/481] curl-openssl.m4: sync LibreSSL detection method with rest of forks Also with actual usage in source. Closes #23197 --- m4/curl-openssl.m4 | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/m4/curl-openssl.m4 b/m4/curl-openssl.m4 index 8a1d07b02a4d..61a2e758acf8 100644 --- a/m4/curl-openssl.m4 +++ b/m4/curl-openssl.m4 @@ -265,8 +265,9 @@ if test "x$OPT_OPENSSL" != "xno"; then AC_LANG_PROGRAM([[ #include ]],[[ - int dummy = LIBRESSL_VERSION_NUMBER; - (void)dummy; + #ifndef LIBRESSL_VERSION_NUMBER + #error not LibreSSL + #endif ]]) ],[ AC_MSG_RESULT([yes]) From 8a9c494664732e02aed9a371df04e783ca085583 Mon Sep 17 00:00:00 2001 From: Viktor Szakats Date: Mon, 28 Sep 2026 23:30:58 +0200 Subject: [PATCH 331/481] apple: sync hash init calls, add comments Hash init functions always return success (1). Stop checking the result in one outlier case (MD5), syncing with the rest (MD4, SHA256). Refs: https://developer-mdn.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man3/CC_MD4_Init.3cc.html https://developer-mdn.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man3/CC_MD5_Init.3cc.html https://developer-mdn.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man3/CC_SHA256_Init.3cc.html Closes #23199 --- lib/md4.c | 2 +- lib/md5.c | 4 +--- lib/sha256.c | 2 +- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/lib/md4.c b/lib/md4.c index 04773c1e96c6..606c501c6f24 100644 --- a/lib/md4.c +++ b/lib/md4.c @@ -113,7 +113,7 @@ typedef CC_MD4_CTX my_md4_ctx; static int my_md4_init(my_md4_ctx *ctx) { - return CC_MD4_Init(ctx); + return CC_MD4_Init(ctx); /* always returns 1 */ } static void my_md4_update(my_md4_ctx *ctx, diff --git a/lib/md5.c b/lib/md5.c index 2f0511712f33..bf47b6c4a8a9 100644 --- a/lib/md5.c +++ b/lib/md5.c @@ -177,9 +177,7 @@ static void my_md5_final(unsigned char *digest, void *ctx) static CURLcode my_md5_init(void *ctx) { - if(!CC_MD5_Init(ctx)) - return CURLE_OUT_OF_MEMORY; - + (void)CC_MD5_Init(ctx); /* always returns 1 */ return CURLE_OK; } diff --git a/lib/sha256.c b/lib/sha256.c index d6de41a08bb7..a6b1f1373cb6 100644 --- a/lib/sha256.c +++ b/lib/sha256.c @@ -179,7 +179,7 @@ typedef CC_SHA256_CTX my_sha256_ctx; static CURLcode my_sha256_init(void *ctx) { - (void)CC_SHA256_Init(ctx); + (void)CC_SHA256_Init(ctx); /* always returns 1 */ return CURLE_OK; } From 2b145f362927331dc5153737728c8dfb61970086 Mon Sep 17 00:00:00 2001 From: Viktor Szakats Date: Wed, 30 Sep 2026 23:14:51 +0200 Subject: [PATCH 332/481] test2007: set `MSYS2_ARG_CONV_EXCL` to avoid flaky failures Necessary after bumping this test from MD5 to SHA256 earlier today. Fixing (seen in 'msvc, CM x64-windows openssl +examples' job): ``` test 2007 [SFTP quote rmdir command with trailing junk] curl returned 60, when expecting 21 [...] ../src/Debug/curl.exe -q --output log/4/curl2007.out --include --trace-ascii log/4/trace2007 --trace-time --hostpubsha256 /Y/tbeENUtVCGxTDQrxHaGGDKF784yL5gxs9++AlgI0 --key log/4/server/curl_client_key --pubkey log/4/server/curl_client_key.pub -u runneradmin: sftp://127.0.0.1:57155/d/a/curl/curl/bld/tests/log/4/file2007.txt -Q "mkdir /tmp/test trailing_garbage" > log/4/stdout2007 2> log/4/stderr2007 [...] curl: (60) Denied establishing ssh session: mismatch SHA256 fingerprint. Remote /Y/tbeENUtVCGxTDQrxHaGGDKF784yL5gxs9++AlgI0= is not equal to Y:/tbeENUtVCGxTDQrxHaGGDKF784yL5gxs9++AlgI0 ``` Ref: https://github.com/curl/curl/actions/runs/36765608048/job/110059238555?pr=23146#step:16:3641 Also: update/sync related comments. Bug: https://github.com/curl/curl/pull/23146#issuecomment-5918359695 Follow-up to 183ea8bbedd6e378ed9496049577e1b519bcae3c #23184 Closes #23200 --- tests/data/test2007 | 5 +++++ tests/data/test3021 | 2 +- tests/data/test3022 | 2 +- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/tests/data/test2007 b/tests/data/test2007 index 4806dfd449f6..9e5db4a8d24c 100644 --- a/tests/data/test2007 +++ b/tests/data/test2007 @@ -22,6 +22,11 @@ SFTP quote rmdir command with trailing junk --hostpubsha256 %SSHSRVSHA256 --key %LOGDIR/server/curl_client_key --pubkey %LOGDIR/server/curl_client_key.pub -u %USER: sftp://%HOSTIP:%SSHPORT%SFTP_PWD/%LOGDIR/file%TESTNUMBER.txt -Q "mkdir /tmp/test trailing_garbage" + +# Needed for MSYS2 to not treat the base64 hash argument as a POSIX path list +# that has to be converted to Windows paths +MSYS2_ARG_CONV_EXCL=* + diff --git a/tests/data/test3021 b/tests/data/test3021 index 1aa973a68843..cddd63fdd73f 100644 --- a/tests/data/test3021 +++ b/tests/data/test3021 @@ -27,7 +27,7 @@ SFTP correct sha256 host key --hostpubsha256 %SSHSRVSHA256 --key %LOGDIR/server/curl_client_key --pubkey %LOGDIR/server/curl_client_key.pub -u %USER: sftp://%HOSTIP:%SSHPORT%SFTP_PWD/%LOGDIR/file%TESTNUMBER.txt -# Needed for MSYS2 to not treat the argument as a POSIX path list +# Needed for MSYS2 to not treat the base64 hash argument as a POSIX path list # that has to be converted to Windows paths MSYS2_ARG_CONV_EXCL=* diff --git a/tests/data/test3022 b/tests/data/test3022 index 6d692a817ef3..817f2bbe3d86 100644 --- a/tests/data/test3022 +++ b/tests/data/test3022 @@ -27,7 +27,7 @@ SCP correct sha256 host key --hostpubsha256 %SSHSRVSHA256 --key %LOGDIR/server/curl_client_key --pubkey %LOGDIR/server/curl_client_key.pub -u %USER: scp://%HOSTIP:%SSHPORT%SCP_PWD/%LOGDIR/file%TESTNUMBER.txt -# Needed for MSYS2 to not treat the argument as a POSIX path list +# Needed for MSYS2 to not treat the base64 hash argument as a POSIX path list # that has to be converted to Windows paths MSYS2_ARG_CONV_EXCL=* From 60cf74f438cfaa4cbf133b1a5f712225a787783c Mon Sep 17 00:00:00 2001 From: Viktor Szakats Date: Mon, 28 Sep 2026 21:12:19 +0200 Subject: [PATCH 333/481] tidy-up: null-terminator, NUL byte Minor steps to improve consistency. Prefer 'null-terminator', NUL (uppercase) byte, null (lowercase) byte. Perfer NUL over zero. Closes #23201 --- RELEASE-NOTES | 2 +- docs/HISTORY.md | 2 +- .../opts/CURLOPT_ABSTRACT_UNIX_SOCKET.md | 4 ++-- docs/libcurl/opts/CURLOPT_HEADERFUNCTION.md | 2 +- docs/libcurl/opts/CURLOPT_HSTSWRITEFUNCTION.md | 17 ++++++++--------- include/curl/curl.h | 2 +- lib/curl_trc.c | 2 +- lib/escape.c | 2 +- lib/ftp.c | 2 +- lib/getenv.c | 3 ++- lib/http.c | 6 +++--- lib/mprintf.c | 14 +++++++------- lib/netrc.c | 2 +- lib/pingpong.c | 4 ++-- lib/socks.c | 2 +- lib/vauth/ntlm.c | 2 +- lib/vdns/asyn-thrdd.c | 2 +- lib/vdns/dnscache.c | 3 ++- lib/vdns/hostip.c | 2 +- lib/vtls/hostcheck.c | 2 +- lib/vtls/schannel_verify.c | 2 +- projects/OS400/ccsidcurl.c | 2 +- src/tool_dirhie.c | 2 +- src/var.c | 6 +++--- tests/data/test2106 | 2 +- tests/data/test2107 | 2 +- tests/data/test2108 | 2 +- tests/data/test2109 | 2 +- tests/data/test35 | 2 +- tests/data/test451 | 2 +- tests/data/test453 | 2 +- tests/data/test456 | 2 +- tests/data/test463 | 2 +- tests/data/test545 | 2 +- tests/data/test792 | 2 +- tests/data/test793 | 2 +- tests/libtest/lib557.c | 6 +++--- tests/server/getpart.c | 4 ++-- tests/unit/unit1397.c | 8 ++++---- tests/unit/unit1658.c | 2 +- 40 files changed, 67 insertions(+), 66 deletions(-) diff --git a/RELEASE-NOTES b/RELEASE-NOTES index 76f16ea49266..b45911c95883 100644 --- a/RELEASE-NOTES +++ b/RELEASE-NOTES @@ -127,7 +127,7 @@ This release includes the following bugfixes: o openssl: use non-deprecated API for MD5/MD4 [69] o os400: document curl_formadd_CCSID multi-chunk peculiarity [5] o os400: restore HAVE_FCHMOD [181] - o ossl_verifyhost: remove assumption of null termination of ASN1_STRING. [26] + o ossl_verifyhost: remove assumption of null-termination of ASN1_STRING. [26] o output-dir.md: expand on its behavior [151] o parsedate: find day names, months and time zones faster [68] o parsedate: remove the unused leading three letters [106] diff --git a/docs/HISTORY.md b/docs/HISTORY.md index 7711c3cd1ddc..9a87c339e05f 100644 --- a/docs/HISTORY.md +++ b/docs/HISTORY.md @@ -245,7 +245,7 @@ March: security vulnerability: libcurl Arbitrary File Access April: added CMake support -August: security vulnerability: libcurl embedded zero in cert name +August: security vulnerability: libcurl embedded zero byte in cert name December: Added support for IMAP, POP3 and SMTP diff --git a/docs/libcurl/opts/CURLOPT_ABSTRACT_UNIX_SOCKET.md b/docs/libcurl/opts/CURLOPT_ABSTRACT_UNIX_SOCKET.md index d13cf7c18076..48167ae323dd 100644 --- a/docs/libcurl/opts/CURLOPT_ABSTRACT_UNIX_SOCKET.md +++ b/docs/libcurl/opts/CURLOPT_ABSTRACT_UNIX_SOCKET.md @@ -30,9 +30,9 @@ CURLcode curl_easy_setopt(CURL *handle, CURLOPT_ABSTRACT_UNIX_SOCKET, Enables the use of an abstract Unix domain socket instead of establishing a TCP connection to a host. The parameter should be a char * to a null-terminated string holding the path of the socket. The path is set to -*path* prefixed by a NULL byte. This is the convention for abstract +*path* prefixed by a null byte. This is the convention for abstract sockets, however it should be stressed that the path passed to this function -should not contain a leading NULL byte. +should not contain a leading null byte. On non-supporting platforms, the abstract address is interpreted as an empty string and fails gracefully, generating a runtime error. diff --git a/docs/libcurl/opts/CURLOPT_HEADERFUNCTION.md b/docs/libcurl/opts/CURLOPT_HEADERFUNCTION.md index 107e9e524c0b..130f78f30bd2 100644 --- a/docs/libcurl/opts/CURLOPT_HEADERFUNCTION.md +++ b/docs/libcurl/opts/CURLOPT_HEADERFUNCTION.md @@ -110,7 +110,7 @@ Nothing. static size_t header_callback(char *buffer, size_t size, size_t nitems, void *userdata) { - /* received header is 'nitems' bytes in 'buffer' NOT NULL-TERMINATED */ + /* received header is 'nitems' bytes in 'buffer' NOT null-terminated */ /* 'userdata' is set with CURLOPT_HEADERDATA */ return nitems; } diff --git a/docs/libcurl/opts/CURLOPT_HSTSWRITEFUNCTION.md b/docs/libcurl/opts/CURLOPT_HSTSWRITEFUNCTION.md index cdbdbe5d2629..180cd39dd2c8 100644 --- a/docs/libcurl/opts/CURLOPT_HSTSWRITEFUNCTION.md +++ b/docs/libcurl/opts/CURLOPT_HSTSWRITEFUNCTION.md @@ -49,17 +49,16 @@ This callback function gets called by libcurl repeatedly to allow the application to store the in-memory HSTS cache when libcurl is about to discard it. -Set the *clientp* argument with the CURLOPT_HSTSWRITEDATA(3) option -or it is NULL. -When the callback is invoked, the *sts* pointer points to a populated -struct: Read the hostname to 'name' (it is *namelen* bytes long and null -terminated. The *includeSubDomains* field is non-zero if the entry matches -subdomains. The *expire* string is a date stamp null-terminated string +Set the *clientp* argument with the CURLOPT_HSTSWRITEDATA(3) option or it is +NULL. When the callback is invoked, the *sts* pointer points to a populated +struct: Read the hostname to 'name' (it is *namelen* bytes long and +null-terminated. The *includeSubDomains* field is non-zero if the entry +matches subdomains. The *expire* string is a date stamp null-terminated string using the syntax YYYYMMDD HH:MM:SS. -The callback should return *CURLSTS_OK* if it succeeded and is prepared to -be called again (for another host) or *CURLSTS_DONE* if there is nothing -more to do. It can also return *CURLSTS_FAIL* to signal error. +The callback should return *CURLSTS_OK* if it succeeded and is prepared to be +called again (for another host) or *CURLSTS_DONE* if there is nothing more to +do. It can also return *CURLSTS_FAIL* to signal error. This option does not enable HSTS, you need to use CURLOPT_HSTS_CTRL(3) to do that. diff --git a/include/curl/curl.h b/include/curl/curl.h index 19e1f40e2bdb..ccc9a09ac864 100644 --- a/include/curl/curl.h +++ b/include/curl/curl.h @@ -1135,7 +1135,7 @@ typedef CURLSTScode (*curl_hstswrite_callback)(CURL *easy, /* CURLOPT aliases that make no runtime difference */ -/* 'char *' argument to a string with a trailing zero */ +/* 'char *' argument to a string with a null-terminator */ #define CURLOPTTYPE_STRINGPOINT CURLOPTTYPE_OBJECTPOINT /* 'struct curl_slist *' argument */ diff --git a/lib/curl_trc.c b/lib/curl_trc.c index 6c967151049b..6d9b2f31aa87 100644 --- a/lib/curl_trc.c +++ b/lib/curl_trc.c @@ -109,7 +109,7 @@ static size_t trc_print_ids(struct Curl_easy *data, char *buf, size_t maxlen) static size_t trc_end_buf(char *buf, size_t len, size_t maxlen, bool addnl) { /* make sure we end the trace line in `buf` properly. It needs - * to end with a terminating '\0' or '\n\0' */ + * to end with a null-terminator '\0' or '\n\0' */ if(len >= (maxlen - (addnl ? 2 : 1))) { len = maxlen - 5; buf[len++] = '.'; diff --git a/lib/escape.c b/lib/escape.c index 64f6e4ab7376..32f94cbb8e2f 100644 --- a/lib/escape.c +++ b/lib/escape.c @@ -199,7 +199,7 @@ char *curl_easy_escape(CURL *curl, const char *string, int length) memcpy(target, f, nappend); target += nappend; } - *target = '\0'; /* null terminate */ + *target = '\0'; /* null-terminate */ return encoded; } diff --git a/lib/ftp.c b/lib/ftp.c index d4810eb15f40..f66d6bd1d351 100644 --- a/lib/ftp.c +++ b/lib/ftp.c @@ -304,7 +304,7 @@ static CURLcode ftp_parse_url_path(struct Curl_easy *data, if(fileName && *fileName) ftpc->file = fileName; else - ftpc->file = NULL; /* instead of point to a zero byte, + ftpc->file = NULL; /* instead of point to a NUL byte, we make it a NULL pointer */ if(data->state.upload && !ftpc->file && (ftp->transfer == PPTRANSFER_BODY)) { diff --git a/lib/getenv.c b/lib/getenv.c index a957e6e80199..acf058e49704 100644 --- a/lib/getenv.c +++ b/lib/getenv.c @@ -56,7 +56,8 @@ char *curl_getenv(const char *variable) return NULL; } - /* if rc < bufsize then rc is bytes written not including null */ + /* if rc < bufsize then rc is bytes written not including + the null-terminator */ if(rc < bufsize) return buf; diff --git a/lib/http.c b/lib/http.c index b4a7f5534132..d9163f17df95 100644 --- a/lib/http.c +++ b/lib/http.c @@ -1067,7 +1067,7 @@ static CURLcode auth_bearer(struct Curl_easy *data, * headers. They are dealt with both in the transfer.c main loop and in the * proxy CONNECT loop. * - * The 'auth' line ends with a null byte without CR or LF present. + * The 'auth' line ends with a NUL byte without CR or LF present. */ CURLcode Curl_http_input_auth(struct Curl_easy *data, bool proxy, const char *auth) /* the first non-space */ @@ -1481,7 +1481,7 @@ bool Curl_compareheader(const char *headerline, /* line to check */ for(len = curlx_strlen(&val); len >= clen;) { struct Curl_str next; const char *o = p; - /* after a match there must be a comma, space, newline or null byte */ + /* after a match there must be a comma, space, newline or NUL byte */ if(curl_strnequal(p, content, clen) && ((p[clen] == ',') || ISBLANK(p[clen]) || ISNEWLINE(p[clen]) || !p[clen])) @@ -3869,7 +3869,7 @@ CURLcode Curl_verify_header(struct Curl_easy *data, switch(hd[i]) { case '\0': /* this is bad, bail out */ - failf(data, "Nul byte in header"); + failf(data, "NUL byte in header"); return CURLE_WEIRD_SERVER_REPLY; case '\r': if(i < hdlen - 2) { diff --git a/lib/mprintf.c b/lib/mprintf.c index 1ad26d39e2c5..571c4175015d 100644 --- a/lib/mprintf.c +++ b/lib/mprintf.c @@ -127,7 +127,7 @@ struct nsprintf { }; /* Output bytes are staged here and appended to the dynbuf in chunks: the - dynbuf append overhead (bounds checks, growth, memcpy, null termination) + dynbuf append overhead (bounds checks, growth, memcpy, null-termination) once per byte dominates formatting. 512 bytes of stack holds a typical request line or header in a single flush. */ #define ASPRINTF_STAGE_SIZE 512 @@ -716,7 +716,7 @@ static bool stream_pad(void *userp, return stream_run(userp, stream, streamn, src, (size_t)num, donep); } -/* emit the leading NUL-terminated part of a run of at most 'len' bytes. +/* emit the leading null-terminated part of a run of at most 'len' bytes. Returns TRUE to abort formatting. A byte loop and not memchr(): 'len' is the requested precision, which @@ -1013,7 +1013,7 @@ static bool out_string(void *userp, /* With no precision, len is already the exact length; emit it without scanning again. With precision, scan up to len but stop at NUL, since - the string may be shorter or the buffer may not be NUL-terminated. */ + the string may be shorter or the buffer may not be null-terminated. */ if(prec == -1) { if(stream_run(userp, stream, streamn, (const unsigned char *)str, len, donep)) @@ -1256,12 +1256,12 @@ int curl_mvsnprintf(char *buffer, size_t maxlength, const char *format, retcode = formatf(&info, addbyter, addrun, NULL, format, args); if(info.max) { - /* we terminate this with a zero byte */ + /* we null-terminate this */ if(info.max == info.length) { /* we are at maximum, scrap the last letter */ info.buffer[-1] = 0; DEBUGASSERT(retcode); - retcode--; /* do not count the nul byte */ + retcode--; /* do not count the NUL byte */ } else info.buffer[0] = 0; @@ -1448,7 +1448,7 @@ int curl_msprintf(char *buffer, const char *format, ...) va_start(args, format); retcode = formatf(&buffer, storebuffer, storerun, NULL, format, args); va_end(args); - *buffer = 0; /* we terminate this with a zero byte */ + *buffer = 0; /* we null-terminate this */ return retcode; } @@ -1508,7 +1508,7 @@ int curl_mprintf(const char *format, ...) int curl_mvsprintf(char *buffer, const char *format, va_list args) { int retcode = formatf(&buffer, storebuffer, storerun, NULL, format, args); - *buffer = 0; /* we terminate this with a zero byte */ + *buffer = 0; /* we null-terminate this */ return retcode; } diff --git a/lib/netrc.c b/lib/netrc.c index b416f376209e..6a6049a7d2eb 100644 --- a/lib/netrc.c +++ b/lib/netrc.c @@ -223,7 +223,7 @@ static NETRCcode netrc_lexer_quoted(struct netrc_lexer *lexer) } else if(c == '\"') { ++s; /* pass the ending quote */ - /* Ensure even an empty literal has a terminating NUL. */ + /* Ensure even an empty literal has a null-terminator. */ result = curlx_dyn_addn(&lexer->literal, "", 0); rc = curl2netrc(result); goto out; diff --git a/lib/pingpong.c b/lib/pingpong.c index b49f9c93ae0a..cf958b748671 100644 --- a/lib/pingpong.c +++ b/lib/pingpong.c @@ -298,8 +298,8 @@ CURLcode Curl_pp_readresp(struct Curl_easy *data, if(memchr(line, 0, length)) { /* The response line is passed on as a "header" below, so reject an - embedded nul the same way verify_header() does for HTTP. */ - failf(data, "Nul byte in server response line"); + embedded NUL byte the same way verify_header() does for HTTP. */ + failf(data, "NUL byte in server response line"); return CURLE_WEIRD_SERVER_REPLY; } diff --git a/lib/socks.c b/lib/socks.c index 06e9673e0b0d..ca7d3e27daa8 100644 --- a/lib/socks.c +++ b/lib/socks.c @@ -293,7 +293,7 @@ static CURLproxycode socks4_req_add_user(struct socks_ctx *sx, failf(data, "Too long SOCKS proxy username"); return CURLPX_LONG_USER; } - /* add proxy name WITH trailing zero */ + /* add proxy name WITH null-terminator */ result = Curl_bufq_cwrite(&sx->iobuf, sx->creds->user, plen + 1, &nwritten); if(result || (nwritten != (plen + 1))) diff --git a/lib/vauth/ntlm.c b/lib/vauth/ntlm.c index 3f1ffb50ce7a..929531ca1740 100644 --- a/lib/vauth/ntlm.c +++ b/lib/vauth/ntlm.c @@ -474,7 +474,7 @@ CURLcode Curl_auth_create_ntlm_type1_message(struct Curl_easy *data, "%c%c" /* 2 zeroes */ "%s" /* hostname */ "%s", /* domain string */ - 0, /* trailing zero */ + 0, /* null-terminator */ 0, 0, 0, /* part of type-1 long */ LONGQUARTET(NTLMFLAG_NEGOTIATE_OEM | diff --git a/lib/vdns/asyn-thrdd.c b/lib/vdns/asyn-thrdd.c index 8fb5b1a5ec0f..b344f83026e8 100644 --- a/lib/vdns/asyn-thrdd.c +++ b/lib/vdns/asyn-thrdd.c @@ -150,7 +150,7 @@ static struct async_thrdd_item *async_thrdd_item_create( if(!item) return NULL; - if(hostlen) /* NUL byte of name already in struct size */ + if(hostlen) /* null-terminator of name already in struct size */ memcpy(item->hostname, hostname, hostlen); item->mid = data->mid; item->resolv_id = resolv_id; diff --git a/lib/vdns/dnscache.c b/lib/vdns/dnscache.c index 08570a80ed7a..5949dfef1d14 100644 --- a/lib/vdns/dnscache.c +++ b/lib/vdns/dnscache.c @@ -451,7 +451,8 @@ static struct Curl_dns_entry *dnsc_entry_create(struct dnsc_id *pid, if(curlx_strlen(&pid->name) > UINT16_MAX) goto out; - /* Create a new cache entry, struct already has the hostname NUL */ + /* Create a new cache entry, + struct already has the hostname null-terminator */ dns = curlx_calloc(1, sizeof(struct Curl_dns_entry) + curlx_strlen(&pid->name)); if(!dns) diff --git a/lib/vdns/hostip.c b/lib/vdns/hostip.c index ce893aeccfcb..222e86b58cb6 100644 --- a/lib/vdns/hostip.c +++ b/lib/vdns/hostip.c @@ -395,7 +395,7 @@ static struct Curl_resolv_async *hostip_async_new(struct Curl_easy *data, return NULL; } - /* struct size already includes the NUL for hostname */ + /* struct size already includes the null-terminator for hostname */ async = curlx_calloc(1, sizeof(*async)); if(!async) return NULL; diff --git a/lib/vtls/hostcheck.c b/lib/vtls/hostcheck.c index 8fed51f6e934..32690a80ef84 100644 --- a/lib/vtls/hostcheck.c +++ b/lib/vtls/hostcheck.c @@ -66,7 +66,7 @@ static bool pmatch(const char *hostname, size_t hostlen, * Only match on "*" being used for the leftmost label, not "a*", "a*b" nor * "*b". * - * The pattern string does not necessarily need to be NUL-terminated. + * The pattern string does not necessarily need to be null-terminated. * * Return TRUE on a match. FALSE if not. * diff --git a/lib/vtls/schannel_verify.c b/lib/vtls/schannel_verify.c index 3bc4be9e779b..3e4470cca80b 100644 --- a/lib/vtls/schannel_verify.c +++ b/lib/vtls/schannel_verify.c @@ -541,7 +541,7 @@ CURLcode Curl_verify_host(struct Curl_cfilter *cf, struct Curl_easy *data) } /* CertGetNameString guarantees that the returned name does not contain - * embedded null bytes. This appears to be undocumented behavior. + * embedded NUL bytes. This appears to be undocumented behavior. */ cert_hostname_buff = curlx_malloc(len * sizeof(TCHAR)); if(!cert_hostname_buff) { diff --git a/projects/OS400/ccsidcurl.c b/projects/OS400/ccsidcurl.c index 02ead8934c04..d5d705a7f8ea 100644 --- a/projects/OS400/ccsidcurl.c +++ b/projects/OS400/ccsidcurl.c @@ -213,7 +213,7 @@ static CURLcode dyn_addn_CCSID(struct dynbuf *db, dlen = curlx_dyn_len(db); if(len == CURL_ZERO_TERMINATED) { - /* The null terminator has been converted AND counted as a character. + /* The null-terminator has been converted AND counted as a character. * Measure it by an additional conversion and drop it. */ int tlen = convert(buffer, sizeof(buffer), universal_terminator, 1, ASCII_CCSID, ccsidout); diff --git a/src/tool_dirhie.c b/src/tool_dirhie.c index 82377a718edc..6e0a0e5e3763 100644 --- a/src/tool_dirhie.c +++ b/src/tool_dirhie.c @@ -117,7 +117,7 @@ CURLcode create_dir_hierarchy(const char *outfile) size_t seplen = strspn(outfile, PATH_DELIMITERS); size_t len = strcspn(&outfile[seplen], PATH_DELIMITERS); - /* the last path component is the file and it ends with a null byte */ + /* the last path component is the file and it ends with a NUL byte */ if(!outfile[len + seplen]) break; diff --git a/src/var.c b/src/var.c index 1b6494709f05..d5015bd72681 100644 --- a/src/var.c +++ b/src/var.c @@ -300,11 +300,11 @@ ParameterError varexpand(const char *line, struct dynbuf *out, bool *replaced) } if(value && vlen > 0) { - /* A variable might contain null bytes. Such bytes cannot be shown + /* A variable might contain NUL bytes. Such bytes cannot be shown using normal means, this is an error. */ const char *nb = memchr(value, '\0', vlen); if(nb) { - errorf("variable contains null byte"); + errorf("variable contains NUL byte"); return PARAM_EXPAND_ERROR; } } @@ -352,7 +352,7 @@ static ParameterError addvariable(const char *name, p = curlx_calloc(1, sizeof(struct tool_var) + nlen); if(p) { memcpy(p->name, name, nlen); - /* the null-termination byte is already present from above */ + /* the null-terminator is already present from above */ p->content = contalloc ? content : curlx_memdup0(content, clen); if(p->content) { diff --git a/tests/data/test2106 b/tests/data/test2106 index 40d2dc8b62b6..6df6ab7b3b9f 100644 --- a/tests/data/test2106 +++ b/tests/data/test2106 @@ -30,7 +30,7 @@ chunky-trailer: he%hex[%00]hex%llo%CR http -HTTP chunked response with a nul byte in the trailer +HTTP chunked response with a NUL byte in the trailer http://%HOSTIP:%HTTPPORT/%TESTNUMBER diff --git a/tests/data/test2107 b/tests/data/test2107 index 8c4404410997..0a177b893982 100644 --- a/tests/data/test2107 +++ b/tests/data/test2107 @@ -33,7 +33,7 @@ http-proxy proxy -HTTP CONNECT response with a nul byte in a header +HTTP CONNECT response with a NUL byte in a header http://%HOSTIP:%HTTPPORT/%TESTNUMBER -p -x http://%HOSTIP:%PROXYPORT diff --git a/tests/data/test2108 b/tests/data/test2108 index 481d09357e73..4cac52eede21 100644 --- a/tests/data/test2108 +++ b/tests/data/test2108 @@ -18,7 +18,7 @@ REPLY PASS 230 logged\x00 in ftp -FTP rejects a nul byte in a server response line +FTP rejects a NUL byte in a server response line ftp://%HOSTIP:%FTPPORT/%TESTNUMBER diff --git a/tests/data/test2109 b/tests/data/test2109 index 323ea144c4d2..1c7cc70351b7 100644 --- a/tests/data/test2109 +++ b/tests/data/test2109 @@ -38,7 +38,7 @@ mqtt://%HOSTIP:%MQTTPORT/aa%00bb s/^(.* 00044d5154540402003c000c6375726c).*/$1/ -# the topic is decoded and the embedded nul rejected before SUBSCRIBE is sent +# the topic is decoded and the embedded NUL byte rejected before SUBSCRIBE is sent client CONNECT 18 00044d5154540402003c000c6375726c server CONNACK 2 20020000 diff --git a/tests/data/test35 b/tests/data/test35 index 04f5093c0dcb..9224f1284d31 100644 --- a/tests/data/test35 +++ b/tests/data/test35 @@ -24,7 +24,7 @@ blablabla http -HTTP POST binary data with embedded NUL +HTTP POST binary data with embedded NUL byte http://%HOSTIP:%HTTPPORT/we/want/%TESTNUMBER --data-binary @%LOGDIR/test%TESTNUMBER.txt diff --git a/tests/data/test451 b/tests/data/test451 index 3b9e30a7c4a8..1345bacdf930 100644 --- a/tests/data/test451 +++ b/tests/data/test451 @@ -31,7 +31,7 @@ Funny-head: yesyes http -Variable from file that is JSON and URL encoded (with null byte) +Variable from file that is JSON and URL encoded (with NUL byte) %hex[%01%02%03%00%04%05%06]hex% diff --git a/tests/data/test453 b/tests/data/test453 index 9dab3ba33135..b8574cddaddb 100644 --- a/tests/data/test453 +++ b/tests/data/test453 @@ -13,7 +13,7 @@ variables http -Variable output containing null byte +Variable output containing NUL byte %hex[%01%02%03%00%04%05%06]hex% diff --git a/tests/data/test456 b/tests/data/test456 index 169cf3882ca1..4f9b59ddf9bc 100644 --- a/tests/data/test456 +++ b/tests/data/test456 @@ -13,7 +13,7 @@ variables http -Variable output starting with null byte +Variable output starting with NUL byte %hex[%00]hex%hello diff --git a/tests/data/test463 b/tests/data/test463 index 4cd99d546c78..05191e9dbc83 100644 --- a/tests/data/test463 +++ b/tests/data/test463 @@ -24,7 +24,7 @@ ok! http -HTTP with -d @file with file containing CR, LF and null byte +HTTP with -d @file with file containing CR, LF and NUL byte %hex[one%0dtwo%0dthree]hex% diff --git a/tests/data/test545 b/tests/data/test545 index d25465048b0e..d7cf3b6c5c38 100644 --- a/tests/data/test545 +++ b/tests/data/test545 @@ -31,7 +31,7 @@ lib544 -HTTP POST binary with embedded zero and no trailing zero +HTTP POST binary with embedded NUL byte and no null-terminator http://%HOSTIP:%HTTPPORT/%TESTNUMBER diff --git a/tests/data/test792 b/tests/data/test792 index 1763ffc781d9..c817fdb42ff0 100644 --- a/tests/data/test792 +++ b/tests/data/test792 @@ -22,7 +22,7 @@ ftp proxy -.netrc with embedded NULL byte +.netrc with embedded NUL byte --netrc --netrc-file %LOGDIR/netrc%TESTNUMBER ftp://%HOSTIP:%FTPPORT/%TESTNUMBER diff --git a/tests/data/test793 b/tests/data/test793 index b293a8e3fbb7..9111ef90c3e2 100644 --- a/tests/data/test793 +++ b/tests/data/test793 @@ -22,7 +22,7 @@ ftp proxy -.netrc with embedded NULL byte, with quoted token +.netrc with embedded NUL byte, with quoted token --netrc --netrc-file %LOGDIR/netrc%TESTNUMBER ftp://%HOSTIP:%FTPPORT/%TESTNUMBER diff --git a/tests/libtest/lib557.c b/tests/libtest/lib557.c index 535e37d4acc8..851776183889 100644 --- a/tests/libtest/lib557.c +++ b/tests/libtest/lib557.c @@ -1280,17 +1280,17 @@ static int test_return_codes(void) if(rc != 5) return 1; - /* returns the length excluding the nul byte */ + /* returns the length excluding the NUL byte */ rc = curl_msnprintf(buf, 5, "%d", 99999); if(rc != 4) return 1; - /* returns the length excluding the nul byte */ + /* returns the length excluding the NUL byte */ rc = curl_msnprintf(buf, 5, "%s", "helloooooooo"); if(rc != 4) return 1; - /* returns the length excluding the nul byte */ + /* returns the length excluding the NUL byte */ rc = curl_msnprintf(buf, 6, "%s", "helloooooooo"); if(rc != 5) return 1; diff --git a/tests/server/getpart.c b/tests/server/getpart.c index 0cc95f633366..463e829497bd 100644 --- a/tests/server/getpart.c +++ b/tests/server/getpart.c @@ -37,7 +37,7 @@ * line_length() * * Counts the number of characters in a line including a new line. - * Unlike strlen() it does not stop at nul bytes. + * Unlike strlen() it does not stop at NUL bytes. * */ static size_t line_length(const char *buffer, int bytestocheck) @@ -122,7 +122,7 @@ static int readline(char **buffer, size_t *bufsize, size_t *length, * This appends data from a given source buffer to the end of the used part of * a destination buffer. Arguments relative to the destination buffer are, the * address of a pointer to the destination buffer 'dst_buf', the length of data - * in destination buffer excluding potential null string termination 'dst_len', + * in destination buffer excluding potential string null-terminator 'dst_len', * the allocated size of destination buffer 'dst_alloc'. All three destination * buffer arguments may be modified by this function. Arguments relative to the * source buffer are, a pointer to the source buffer 'src_buf' and indication diff --git a/tests/unit/unit1397.c b/tests/unit/unit1397.c index 4ec640878b5e..ba11722bbf46 100644 --- a/tests/unit/unit1397.c +++ b/tests/unit/unit1397.c @@ -112,10 +112,10 @@ static CURLcode test_unit1397(const char *arg) unitfail++; } - /* Curl_cert_hostcheck() should not rely on trailing NULs in the pattern. - * The underlying TLS library might not make a NUL-terminated copy of the - * pattern. Copy it to a new buffer so ASan and valgrind can flag invalid - * accesses. */ + /* Curl_cert_hostcheck() should not rely on null-terminators in the + * pattern. The underlying TLS library might not make a null-terminated + * copy of the pattern. Copy it to a new buffer so ASan and valgrind can + * flag invalid accesses. */ pattern_len = strlen(tests[i].pattern); pattern = pattern_len ? curlx_memdup(tests[i].pattern, pattern_len) : NULL; abort_unless(pattern_len == 0 || pattern, "Out of memory"); diff --git a/tests/unit/unit1658.c b/tests/unit/unit1658.c index f8d2531dc05a..fa7e7cc01855 100644 --- a/tests/unit/unit1658.c +++ b/tests/unit/unit1658.c @@ -205,7 +205,7 @@ static CURLcode test_unit1658(const char *arg) "r:8|" }, { - "rname with null byte", + "rname with NUL byte", (const unsigned char *)"\x00\x00" /* 16-bit prio */ "\x04sa\x00e\x04some\x00", /* RNAME */ 13, From 0e017e0f9bfbf51499bcffb604c3efa1f795328e Mon Sep 17 00:00:00 2001 From: Viktor Szakats Date: Thu, 1 Oct 2026 09:11:17 +0200 Subject: [PATCH 334/481] GHA/linux: move GCC analyzer to the 2nd valgrind slice The 2nd slice completes faster than the 1st; use that room to run the GCC analyzer, leveling off the two slices and making the 1st slice finish earlier. Before: 8m24s / 5m19s After: 6m8s / 6m23s Follow-up to 6d87eb2878899ec1ccf6b612d950b90367b5ea45 #20921 Closes #23205 --- .github/workflows/linux.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index eb8a695654d1..4060b164a051 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -224,19 +224,19 @@ jobs: install_steps: pytest configure: --with-openssl --enable-debug --disable-unity - - name: 'openssl libssh2 sync-resolver valgrind 1 +analyzer' + - name: 'openssl libssh2 sync-resolver valgrind 1' image: ubuntu-26.04-arm install_packages: gcc-16 libidn2-dev libssh2-1-dev libnghttp2-dev libldap-dev valgrind CC: gcc-16 tflags: '--subset=0/2' - generate: -DENABLE_DEBUG=ON -DENABLE_THREADED_RESOLVER=OFF -DCURL_ENABLE_NTLM=ON -DCURL_GCC_ANALYZER=ON + generate: -DENABLE_DEBUG=ON -DENABLE_THREADED_RESOLVER=OFF -DCURL_ENABLE_NTLM=ON - - name: 'openssl libssh2 sync-resolver valgrind 2' + - name: 'openssl libssh2 sync-resolver valgrind 2 +analyzer' image: ubuntu-26.04-arm install_packages: gcc-16 libidn2-dev libssh2-1-dev libnghttp2-dev libldap-dev valgrind CC: gcc-16 tflags: '--subset=1/2' - generate: -DENABLE_DEBUG=ON -DENABLE_THREADED_RESOLVER=OFF -DCURL_ENABLE_NTLM=ON + generate: -DENABLE_DEBUG=ON -DENABLE_THREADED_RESOLVER=OFF -DCURL_ENABLE_NTLM=ON -DCURL_GCC_ANALYZER=ON - name: 'openssl intel C89' image: ubuntu-24.04 From ff1fa030d8aa27d59c1deb28cded2c9768624f56 Mon Sep 17 00:00:00 2001 From: Filippo Tedeschi Date: Tue, 29 Sep 2026 10:47:32 +0200 Subject: [PATCH 335/481] parsedate: validate NULL date pointer in curl_getdate - Return -1 on null datestring. - Extend libtest lib517 to verify. Closes https://github.com/curl/curl/pull/23156 --- lib/parsedate.c | 6 +++++- tests/libtest/lib517.c | 5 +++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/lib/parsedate.c b/lib/parsedate.c index ef7ddd057141..3c11d76ad925 100644 --- a/lib/parsedate.c +++ b/lib/parsedate.c @@ -718,9 +718,13 @@ static int parsedate(const char *date, time_t *output) time_t curl_getdate(const char *p, const time_t *unused) { time_t parsed = -1; - int rc = parsedate(p, &parsed); + int rc; (void)unused; /* legacy argument from the past that we ignore */ + if(!p) + return -1; + + rc = parsedate(p, &parsed); if(rc == PARSEDATE_OK) { if(parsed == (time_t)-1) /* avoid returning -1 for a working scenario */ diff --git a/tests/libtest/lib517.c b/tests/libtest/lib517.c index 38abfc3f8a81..aa15af6c9d61 100644 --- a/tests/libtest/lib517.c +++ b/tests/libtest/lib517.c @@ -257,5 +257,10 @@ static CURLcode test_lib517(const char *URL) } } + if(curl_getdate(NULL, NULL) != (time_t)-1) { + curl_mprintf("WRONGLY parsed NULL string\n"); + error++; + } + return error == 0 ? CURLE_OK : TEST_ERR_FAILURE; } From 94390cdb0281e7882002e1d31fa4d3a41d0744b4 Mon Sep 17 00:00:00 2001 From: Stefan Eissing Date: Tue, 29 Sep 2026 12:08:56 +0200 Subject: [PATCH 336/481] http_chunks: limit trailers size Use `Curl_bump_headersize()` also for chunked trailers so that max header limit is enforced. reported-by: Joshua Rogers Closes https://github.com/curl/curl/pull/23162 --- lib/http_chunks.c | 8 ++++++++ lib/http_chunks.h | 3 ++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/lib/http_chunks.c b/lib/http_chunks.c index 95929d27ba05..2a84ce57c358 100644 --- a/lib/http_chunks.c +++ b/lib/http_chunks.c @@ -282,6 +282,12 @@ static CURLcode httpchunk_readwrite(struct Curl_easy *data, ch->last_code = CHUNKE_PASSTHRU_ERROR; return result; } + result = Curl_bump_headersize(data, trlen, FALSE); + if(result) { + ch->state = CHUNK_FAILED; + ch->last_code = CHUNKE_TRAILER_LIMIT; + return result; + } } curlx_dyn_reset(&ch->trailer); ch->state = CHUNK_TRAILER_CR; @@ -385,6 +391,8 @@ static const char *Curl_chunked_strerror(CHUNKcode code) return "Bad content-encoding found"; case CHUNKE_OUT_OF_MEMORY: return "Out of memory"; + case CHUNKE_TRAILER_LIMIT: + return "trailers exceeded header limit"; } } diff --git a/lib/http_chunks.h b/lib/http_chunks.h index 0cb1f8834093..8f6086b61e94 100644 --- a/lib/http_chunks.h +++ b/lib/http_chunks.h @@ -88,7 +88,8 @@ typedef enum { CHUNKE_BAD_CHUNK, CHUNKE_BAD_ENCODING, CHUNKE_OUT_OF_MEMORY, - CHUNKE_PASSTHRU_ERROR /* Curl_httpchunk_read() returns a CURLcode to use */ + CHUNKE_PASSTHRU_ERROR, /* Curl_httpchunk_read() returns a CURLcode to use */ + CHUNKE_TRAILER_LIMIT /* Max header limit exceeded by trailers */ } CHUNKcode; struct Curl_chunker { From 5057074f70491b15d254e97f411df389072fbcd6 Mon Sep 17 00:00:00 2001 From: Stefan Eissing Date: Tue, 29 Sep 2026 12:53:02 +0200 Subject: [PATCH 337/481] http2: limit trailers and headers count Add `MAX_HTTP_RESP_HEADER_COUNT` limit to headers and trailers being processed by http2 filters. reported-by: Joshua Rogers Closes https://github.com/curl/curl/pull/23165 --- lib/cf-h2-proxy.c | 2 +- lib/http2.c | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/lib/cf-h2-proxy.c b/lib/cf-h2-proxy.c index 6dc45acf64bd..b10e785ea942 100644 --- a/lib/cf-h2-proxy.c +++ b/lib/cf-h2-proxy.c @@ -725,7 +725,7 @@ static CURLcode proxy_h2_submit( CURLcode result; (void)cf; - Curl_dynhds_init(&h2_headers, 0, DYN_HTTP_REQUEST); + Curl_dynhds_init(&h2_headers, MAX_HTTP_RESP_HEADER_COUNT, DYN_HTTP_REQUEST); result = Curl_http_req_to_h2(&h2_headers, req, data); if(result) goto out; diff --git a/lib/http2.c b/lib/http2.c index 4c8b312fe7d8..51af1c2c5b1e 100644 --- a/lib/http2.c +++ b/lib/http2.c @@ -270,7 +270,8 @@ static struct h2_stream_ctx *h2_stream_ctx_create(struct cf_h2_ctx *ctx) Curl_bufq_initp(&stream->sendbuf, &ctx->stream_bufcp, H2_STREAM_SEND_CHUNKS, BUFQ_OPT_NONE); Curl_h1_req_parse_init(&stream->h1, H1_PARSE_DEFAULT_MAX_LINE_LEN); - Curl_dynhds_init(&stream->resp_trailers, 0, DYN_HTTP_REQUEST); + Curl_dynhds_init(&stream->resp_trailers, + MAX_HTTP_RESP_HEADER_COUNT, DYN_HTTP_REQUEST); stream->bodystarted = FALSE; stream->status_code = -1; stream->closed = FALSE; @@ -1750,6 +1751,9 @@ static CURLcode http2_handle_stream_close(struct Curl_cfilter *cf, curlx_dyn_ptr(&dbuf), curlx_dyn_len(&dbuf)); if(result) break; + result = Curl_bump_headersize(data, curlx_dyn_len(&dbuf), FALSE); + if(result) + return result; } curlx_dyn_free(&dbuf); if(result) @@ -2088,7 +2092,7 @@ static CURLcode h2_submit(struct h2_stream_ctx **pstream, uint32_t initial_win_size; *pnwritten = 0; - Curl_dynhds_init(&h2_headers, 0, DYN_HTTP_REQUEST); + Curl_dynhds_init(&h2_headers, MAX_HTTP_RESP_HEADER_COUNT, DYN_HTTP_REQUEST); result = http2_data_setup(cf, data, &stream); if(result) From a65d5ae6923c82e6c0a2e908053907489e4c642a Mon Sep 17 00:00:00 2001 From: Stefan Eissing Date: Tue, 29 Sep 2026 13:07:43 +0200 Subject: [PATCH 338/481] socketpair: fix accept loop - check elapsed time on each loop iteration - act on socket closed by client ("unexpected close") reported-by: Joshua Rogers Closes https://github.com/curl/curl/pull/23166 --- lib/socketpair.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/lib/socketpair.c b/lib/socketpair.c index bcd755a98426..bbd6cbac76b3 100644 --- a/lib/socketpair.c +++ b/lib/socketpair.c @@ -232,17 +232,21 @@ static int wakeup_inet(curl_socket_t socks[2], bool nonblocking) do { ssize_t nread; + /* Do not block forever */ + if(curlx_timediff_ms(curlx_now(), start) > (60 * 1000)) + goto error; + pfd[0].fd = socks[1]; pfd[0].events = POLLIN; pfd[0].revents = 0; (void)Curl_poll(pfd, 1, 1000); /* one second */ nread = sread(socks[1], p, s); - if(nread == -1) { + if(!nread) { /* unexpected close before getting complete `check` */ + goto error; + } + else if(nread == -1) { /* error or close */ int sockerr = SOCKERRNO; - /* Do not block forever */ - if(curlx_timediff_ms(curlx_now(), start) > (60 * 1000)) - goto error; if(SOCK_EAGAIN(sockerr) #ifndef USE_WINSOCK || (sockerr == SOCKEINTR) || (sockerr == SOCKEINPROGRESS) From 62a6ee5f967020a60dc8d4f367b20b6d2c30181a Mon Sep 17 00:00:00 2001 From: Stefan Eissing Date: Tue, 29 Sep 2026 14:04:50 +0200 Subject: [PATCH 339/481] dnscache: check hostname on lookup Since dnscache keys may truncate hostnames longer than `MAX_HOSTCACHE_LEN`, re-check the hostname of a found entry on lookup if it is indeed for the intended peer and ignore it otherwise. reported-by: Joshua Rogers Closes https://github.com/curl/curl/pull/23169 --- lib/vdns/dnscache.c | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/lib/vdns/dnscache.c b/lib/vdns/dnscache.c index 5949dfef1d14..f7e6ad5cfcf9 100644 --- a/lib/vdns/dnscache.c +++ b/lib/vdns/dnscache.c @@ -87,8 +87,8 @@ UNITTEST void dnsc_id2key(struct dnsc_key *key, struct dnsc_id *id); UNITTEST void dnsc_id2key(struct dnsc_key *key, struct dnsc_id *id) { size_t namelen = curlx_strlen(&id->name); - if(namelen > (sizeof(key->data) - 3)) - namelen = sizeof(key->data) - 3; + if(namelen > (MAX_HOSTCACHE_LEN - 3)) + namelen = MAX_HOSTCACHE_LEN - 3; /* store and lower case the name */ key->data[0] = id->type; key->data[1] = (uint8_t)((id->port >> 8) & 0xff); @@ -97,6 +97,19 @@ UNITTEST void dnsc_id2key(struct dnsc_key *key, struct dnsc_id *id) key->len = namelen + 3; } +static bool dnsc_id_matches_entry(struct dnsc_id *id, + struct Curl_dns_entry *dns) +{ + return dns && + /* hash key was not truncated OR */ + ((dns->hostlen <= (MAX_HOSTCACHE_LEN - 3)) || + /* port and host string are the same, case insensitive */ + ((id->port == dns->port) && (id->name.len == dns->hostlen) && + /* for most lookups, case is the same, try that first */ + (!strcmp(id->name.str, dns->hostname) || + curl_strequal(id->name.str, dns->hostname)))); +} + static void dnscache_entry_free(struct Curl_dns_entry *dns) { Curl_freeaddrinfo(dns->addr); @@ -261,6 +274,11 @@ static CURLcode fetch_entry(struct Curl_easy *data, /* See if it is already in our dns cache */ dns = Curl_hash_pick(&dnscache->entries, key.data, key.len); + if(dns && !dnsc_id_matches_entry(&id, dns)) { + /* May happen on hostnames longer than MAX_HOSTCACHE_LEN */ + dns = NULL; + } + /* No entry found in cache, check if we might have a wildcard entry */ if(!dns && (type == CURL_DNST_ADDR) && data->state.wildcard_resolve) { struct Curl_str wildname; From 146698eac0de201aaf47740eab618f16e94ebef0 Mon Sep 17 00:00:00 2001 From: Stefan Eissing Date: Wed, 30 Sep 2026 15:07:05 +0200 Subject: [PATCH 340/481] conncache: never discard a connection still in use Discarding a connection still in use by transfers is a programming error, regardless if the connection has been aborted or not. Deny that unconditionally to not risk use-after-free issues and add a DEBUGASSERT() to catch this mistake. reported-by: Joshua Rogers Closes https://github.com/curl/curl/pull/23189 --- lib/conncache.c | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/lib/conncache.c b/lib/conncache.c index 6e695c4975f8..2f759bed2f01 100644 --- a/lib/conncache.c +++ b/lib/conncache.c @@ -198,14 +198,13 @@ static void cpool_discard_conn(struct cpool *cpool, DEBUGASSERT(conn->cpid == UINT32_MAX); admin = Curl_get_admin(data); - /* - * If this connection is not marked to force-close, leave it open if there - * are other users of it - */ - if(CONN_INUSE(conn) && !aborted) { + /* We MUST NOT discard a connection that is still in use and would + * trigger Use-After-Free. */ + if(CONN_INUSE(conn)) { CURL_TRC_M(admin, "[CPOOL] not discarding #%" FMT_OFF_T " still in use by %u transfers", conn->connection_id, conn->attached_xfers); + DEBUGASSERT(0); return; } From 888a26ab4b47e7207271cc4f0eed308efd4343f4 Mon Sep 17 00:00:00 2001 From: Stefan Eissing Date: Wed, 30 Sep 2026 15:46:31 +0200 Subject: [PATCH 341/481] urldata: shrink connectdata negotiate states - Change members to `uint8_t`. - Replace enum with defines in CURL_ namespace. Closes https://github.com/curl/curl/pull/23192 --- lib/http.c | 20 ++++++++++---------- lib/http_negotiate.c | 28 ++++++++++++++-------------- lib/multi.c | 4 ++-- lib/url.c | 4 ++-- lib/urldata.h | 26 +++++++++++--------------- 5 files changed, 39 insertions(+), 43 deletions(-) diff --git a/lib/http.c b/lib/http.c index d9163f17df95..0f201c6c509e 100644 --- a/lib/http.c +++ b/lib/http.c @@ -446,8 +446,8 @@ static CURLcode http_perhapsrewind(struct Curl_easy *data, if((data->state.authproxy.picked == CURLAUTH_NEGOTIATE) || (data->state.authhost.picked == CURLAUTH_NEGOTIATE)) { VERBOSE(ongoing_auth = "NEGOTIATE"); - if((conn->http_negotiate_state != GSS_AUTHNONE) || - (conn->proxy_negotiate_state != GSS_AUTHNONE)) { + if((conn->http_negotiate_state != CURL_NEGO_AUTH_NONE) || + (conn->proxy_negotiate_state != CURL_NEGO_AUTH_NONE)) { /* The NEGOTIATE-negotiation has started, keep on sending. * Need to do further work on same connection */ abort_upload = FALSE; @@ -945,7 +945,7 @@ static CURLcode auth_spnego(struct Curl_easy *data, if(authp->picked == CURLAUTH_NEGOTIATE) { struct connectdata *conn = data->conn; CURLcode result = Curl_input_negotiate(data, conn, proxy, auth); - curlnegotiate *negstate = proxy ? &conn->proxy_negotiate_state : + uint8_t *negstate = proxy ? &conn->proxy_negotiate_state : &conn->http_negotiate_state; if(!result) { curlx_free(data->req.newurl); @@ -954,7 +954,7 @@ static CURLcode auth_spnego(struct Curl_easy *data, return CURLE_OUT_OF_MEMORY; data->state.authproblem = FALSE; /* we received a GSS auth token and we dealt with it fine */ - *negstate = GSS_AUTHRECV; + *negstate = CURL_NEGO_AUTH_RECV; } else data->state.authproblem = TRUE; @@ -4043,19 +4043,19 @@ static void http_check_auth_closure(struct Curl_easy *data, #ifdef USE_SPNEGO if(conn->bits.close && (((data->req.httpcode == 401) && - (conn->http_negotiate_state == GSS_AUTHRECV)) || + (conn->http_negotiate_state == CURL_NEGO_AUTH_RECV)) || ((data->req.httpcode == 407) && - (conn->proxy_negotiate_state == GSS_AUTHRECV)))) { + (conn->proxy_negotiate_state == CURL_NEGO_AUTH_RECV)))) { infof(data, "Connection closure while negotiating auth (HTTP 1.0?)"); data->state.authproblem = TRUE; } - if((conn->http_negotiate_state == GSS_AUTHDONE) && + if((conn->http_negotiate_state == CURL_NEGO_AUTH_DONE) && (data->req.httpcode != 401)) { - conn->http_negotiate_state = GSS_AUTHSUCC; + conn->http_negotiate_state = CURL_NEGO_AUTH_SUCC; } - if((conn->proxy_negotiate_state == GSS_AUTHDONE) && + if((conn->proxy_negotiate_state == CURL_NEGO_AUTH_DONE) && (data->req.httpcode != 407)) { - conn->proxy_negotiate_state = GSS_AUTHSUCC; + conn->proxy_negotiate_state = CURL_NEGO_AUTH_SUCC; } #endif } diff --git a/lib/http_negotiate.c b/lib/http_negotiate.c index 5e8798134f55..66c7af47f810 100644 --- a/lib/http_negotiate.c +++ b/lib/http_negotiate.c @@ -39,9 +39,9 @@ static void http_auth_nego_reset(struct connectdata *conn, bool proxy) { if(proxy) - conn->proxy_negotiate_state = GSS_AUTHNONE; + conn->proxy_negotiate_state = CURL_NEGO_AUTH_NONE; else { - conn->http_negotiate_state = GSS_AUTHNONE; + conn->http_negotiate_state = CURL_NEGO_AUTH_NONE; Curl_peer_unlink(&conn->creds_origin); Curl_creds_unlink(&conn->creds); } @@ -61,7 +61,7 @@ CURLcode Curl_input_negotiate(struct Curl_easy *data, struct connectdata *conn, /* Point to the correct struct with this */ struct negotiatedata *neg_ctx; - curlnegotiate state; + uint8_t state; if(proxy) { #ifndef CURL_DISABLE_PROXY @@ -89,11 +89,11 @@ CURLcode Curl_input_negotiate(struct Curl_easy *data, struct connectdata *conn, len = strlen(header); neg_ctx->havenegdata = len != 0; if(!len) { - if(state == GSS_AUTHSUCC) { + if(state == CURL_NEGO_AUTH_SUCC) { infof(data, "Negotiate auth restarted"); http_auth_nego_reset(conn, neg_ctx, proxy); } - else if(state != GSS_AUTHNONE) { + else if(state != CURL_NEGO_AUTH_NONE) { /* The server rejected our authentication and has not supplied any more negotiation mechanisms */ http_auth_nego_reset(conn, neg_ctx, proxy); @@ -157,7 +157,7 @@ CURLcode Curl_output_negotiate(struct Curl_easy *data, { struct negotiatedata *neg_ctx; struct auth *authp; - curlnegotiate *state; + uint8_t *state; char *base64 = NULL; size_t len = 0; char *userp; @@ -181,21 +181,21 @@ CURLcode Curl_output_negotiate(struct Curl_easy *data, authp->done = FALSE; - if(*state == GSS_AUTHRECV) { + if(*state == CURL_NEGO_AUTH_RECV) { if(neg_ctx->havenegdata) { neg_ctx->havemultiplerequests = TRUE; } } - else if(*state == GSS_AUTHSUCC) { + else if(*state == CURL_NEGO_AUTH_SUCC) { if(!neg_ctx->havenoauthpersist) { neg_ctx->noauthpersist = !neg_ctx->havemultiplerequests; } } if(neg_ctx->noauthpersist || - (*state != GSS_AUTHDONE && *state != GSS_AUTHSUCC)) { + (*state != CURL_NEGO_AUTH_DONE && *state != CURL_NEGO_AUTH_SUCC)) { - if(neg_ctx->noauthpersist && *state == GSS_AUTHSUCC) { + if(neg_ctx->noauthpersist && *state == CURL_NEGO_AUTH_SUCC) { infof(data, "Curl_output_negotiate, " "no persistent authentication: cleanup existing context"); http_auth_nego_reset(conn, neg_ctx, proxy); @@ -236,23 +236,23 @@ CURLcode Curl_output_negotiate(struct Curl_easy *data, return CURLE_OUT_OF_MEMORY; } - *state = GSS_AUTHSENT; + *state = CURL_NEGO_AUTH_SENT; #ifdef HAVE_GSSAPI if(neg_ctx->status == GSS_S_COMPLETE || neg_ctx->status == GSS_S_CONTINUE_NEEDED) { - *state = GSS_AUTHDONE; + *state = CURL_NEGO_AUTH_DONE; } #else #ifdef USE_WINDOWS_SSPI if(neg_ctx->status == SEC_E_OK || neg_ctx->status == SEC_I_CONTINUE_NEEDED) { - *state = GSS_AUTHDONE; + *state = CURL_NEGO_AUTH_DONE; } #endif #endif } - if(*state == GSS_AUTHDONE || *state == GSS_AUTHSUCC) { + if(*state == CURL_NEGO_AUTH_DONE || *state == CURL_NEGO_AUTH_SUCC) { /* connection is already authenticated, * do not send a header in future requests */ authp->done = TRUE; diff --git a/lib/multi.c b/lib/multi.c index 2e782affdf67..ec8fcec21027 100644 --- a/lib/multi.c +++ b/lib/multi.c @@ -612,8 +612,8 @@ static bool multi_conn_should_close(struct connectdata *conn, conn->proxy_ntlm_state == NTLMSTATE_TYPE2) #endif #ifdef USE_SPNEGO - && !(conn->http_negotiate_state == GSS_AUTHRECV || - conn->proxy_negotiate_state == GSS_AUTHRECV) + && !(conn->http_negotiate_state == CURL_NEGO_AUTH_RECV || + conn->proxy_negotiate_state == CURL_NEGO_AUTH_RECV) #endif ) return TRUE; diff --git a/lib/url.c b/lib/url.c index add577bbca0a..3a9464cfe926 100644 --- a/lib/url.c +++ b/lib/url.c @@ -958,7 +958,7 @@ static bool url_match_auth_ntlm(struct connectdata *conn, static bool url_match_auth_nego(struct connectdata *conn, struct url_conn_match *m) { - if(conn->http_negotiate_state != GSS_AUTHNONE) { + if(conn->http_negotiate_state != CURL_NEGO_AUTH_NONE) { /* Connection is using Negotiate. We cannot reuse if transfer * has different Auth input parameters. */ if(!m->want_nego_http || @@ -978,7 +978,7 @@ static bool url_match_auth_nego(struct connectdata *conn, #ifndef CURL_DISABLE_PROXY /* Same for Proxy Negotiate authentication */ - if(conn->proxy_negotiate_state != GSS_AUTHNONE) { + if(conn->proxy_negotiate_state != CURL_NEGO_AUTH_NONE) { if(!m->want_proxy_nego_http || !Curl_creds_same(m->needle->http_proxy.creds, conn->http_proxy.creds)) return FALSE; diff --git a/lib/urldata.h b/lib/urldata.h index ed4b27412877..ab07696f4201 100644 --- a/lib/urldata.h +++ b/lib/urldata.h @@ -169,13 +169,11 @@ typedef enum { NTLMSTATE_LAST } curlntlm; -typedef enum { - GSS_AUTHNONE, - GSS_AUTHRECV, - GSS_AUTHSENT, - GSS_AUTHDONE, - GSS_AUTHSUCC -} curlnegotiate; +#define CURL_NEGO_AUTH_NONE 0U +#define CURL_NEGO_AUTH_RECV 1U +#define CURL_NEGO_AUTH_SENT 2U +#define CURL_NEGO_AUTH_DONE 3U +#define CURL_NEGO_AUTH_SUCC 4U /* * Boolean values that concerns this connection. @@ -299,11 +297,11 @@ struct connectdata { struct proxy_info http_proxy; #endif + /* A connection can have one or two sockets and connection filters. + * The protocol using the 2nd one is FTP for CONTROL+DATA sockets */ struct Curl_cfilter *cfilter[2]; /* connection filters */ Curl_recv *recv[2]; Curl_send *send[2]; - /* A connection can have one or two sockets and connection filters. - * The protocol using the 2nd one is FTP for CONTROL+DATA sockets */ curl_socket_t sock[2]; #define CONN_SOCK_IDX_VALID(i) (((i) >= 0) && ((i) < 2)) @@ -332,11 +330,6 @@ struct connectdata { curlntlm proxy_ntlm_state; #endif -#ifdef USE_SPNEGO - curlnegotiate http_negotiate_state; - curlnegotiate proxy_negotiate_state; -#endif - /* When this connection is created, store the conditions for the local end bind. This is stored before the actual bind and before any connection is made and will serve the purpose of being used for comparison reasons so @@ -373,7 +366,10 @@ struct connectdata { * 0 at start, then one of 09, 10, 11, etc. */ uint8_t httpversion_seen; uint8_t gssapi_delegation; /* inherited from set.gssapi_delegation */ - +#ifdef USE_SPNEGO + uint8_t http_negotiate_state; + uint8_t proxy_negotiate_state; +#endif }; #ifndef CURL_DISABLE_PROXY From c9514155f0ce8b1e7d04d07b9859aab7c95ef1f5 Mon Sep 17 00:00:00 2001 From: Viktor Szakats Date: Thu, 1 Oct 2026 09:37:23 +0200 Subject: [PATCH 342/481] GHA/linux: bump Slackware container, re-enable SSL via GnuTLS Container still lacks modern OpenSSL; to make this CI job more useful, use GnuTLS as a TLS backend. Ref: https://hub.docker.com/layers/andy5995/slackware-build-essential/15.0/images/sha256-c5aeede463b4a52b5f4650078418718f2447bdb23f9a4cac0d5fb262e7b8dc84 Closes #23206 --- .github/workflows/linux.yml | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 4060b164a051..e619a5768fd9 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -465,12 +465,13 @@ jobs: CC: icc configure: --enable-debug --with-openssl - - name: 'Slackware !ssl gssapi gcc' - # Flags used to build the curl Slackware package, except OpenSSL 1.1.0: + - name: 'Slackware GnuTLS gssapi gcc' + # Flags used to build the curl Slackware package: # https://ftpmirror.infania.net/slackware/slackware64-current/source/n/curl/curl.SlackBuild - configure: --enable-debug --without-ssl --with-libssh2 --with-gssapi --enable-ares --without-ca-bundle --with-ca-path=/etc/ssl/certs + # Current curl no longer supports OpenSSL 1.1.0, thus build with GnuTLS instead: + configure: --enable-debug --with-gnutls --with-libssh2 --with-gssapi --enable-ares --without-ca-bundle --with-ca-path=/etc/ssl/certs # Docker Hub image that `container-job` executes in - container: andy5995/slackware-build-essential:15.0@sha256:f4f2242999038a2c2deb4e5727187caaae92502a7daf8353068932621e1ec92f + container: andy5995/slackware-build-essential:15.0@sha256:c5aeede463b4a52b5f4650078418718f2447bdb23f9a4cac0d5fb262e7b8dc84 - name: 'Alpine MUSL https-rr' configure: --enable-debug --with-ssl --with-libssh2 --with-libidn2 --with-gssapi --enable-ldap --with-libpsl --enable-httpsrr --enable-ares --enable-threaded-resolver From bc63c15b0a8ffd46c0e17dd728cdc1d61082c9bf Mon Sep 17 00:00:00 2001 From: Viktor Szakats Date: Wed, 30 Sep 2026 10:42:05 +0200 Subject: [PATCH 343/481] lib: replace `CURLX_FILE_MODE()` with `mode_t` cast `mode_t` is POSIX. Note, Windows `open()` expects `int`, so only use this in non-Windows codepaths. Ref: https://learn.microsoft.com/cpp/c-runtime-library/reference/open-wopen Suggested-by: Dan Fandrich Ref: https://github.com/curl/curl/pull/23158#issuecomment-5897090824 Follow-up to d48f2f4ec867783e611beeb2ad53fbabcb94aa6f #23171 Follow-up to be9e60327625eea381c298c122173e35620f17c0 #23158 Closes #23181 --- lib/curl_fopen.c | 4 ++-- lib/curlx/win32-fopen.h | 7 ------- lib/file.c | 2 +- lib/vquic/vquic.c | 2 +- 4 files changed, 4 insertions(+), 11 deletions(-) diff --git a/lib/curl_fopen.c b/lib/curl_fopen.c index a110ca9fc6b7..10c04179443a 100644 --- a/lib/curl_fopen.c +++ b/lib/curl_fopen.c @@ -128,7 +128,7 @@ CURLcode Curl_fopen(struct Curl_easy *data, const char *filename, _S_IREAD | _S_IWRITE); #else fd = curlx_open(tempstore, O_WRONLY | O_CREAT | O_EXCL, - CURLX_FILE_MODE(S_IRUSR | S_IWUSR | 0600)); + (mode_t)(S_IRUSR | S_IWUSR | 0600)); #endif if(fd == -1) goto fail; @@ -139,7 +139,7 @@ CURLcode Curl_fopen(struct Curl_easy *data, const char *filename, if((curlx_fstat(fd, &nsb) != -1) && (nsb.st_uid == sb.st_uid) && (nsb.st_gid == sb.st_gid)) { /* if the user and group are the same, clone the original mode */ - if(fchmod(fd, CURLX_FILE_MODE(sb.st_mode)) == -1) + if(fchmod(fd, (mode_t)sb.st_mode) == -1) goto fail; } } diff --git a/lib/curlx/win32-fopen.h b/lib/curlx/win32-fopen.h index 06215ad357f2..469d3c881303 100644 --- a/lib/curlx/win32-fopen.h +++ b/lib/curlx/win32-fopen.h @@ -72,13 +72,6 @@ int curlx_win32_rename(const char *oldpath, const char *newpath); #define curlx_rename rename #endif -#if (defined(ANDROID) || defined(__ANDROID__)) && \ - (defined(__i386__) || defined(__arm__)) -#define CURLX_FILE_MODE(x) ((mode_t)(x)) -#else -#define CURLX_FILE_MODE(x) (x) -#endif - #ifdef CURL_MEMDEBUG #define curlx_fopen(file, mode) curl_dbg_fopen(file, mode, __LINE__, __FILE__) #define curlx_freopen(file, mode, fh) \ diff --git a/lib/file.c b/lib/file.c index 261801632f54..e4ff57378225 100644 --- a/lib/file.c +++ b/lib/file.c @@ -335,7 +335,7 @@ static CURLcode file_upload(struct Curl_easy *data, #ifdef _WIN32 data->set.new_file_perms & (_S_IREAD | _S_IWRITE) #else - CURLX_FILE_MODE(data->set.new_file_perms) + (mode_t)data->set.new_file_perms #endif ); if(fd < 0) { diff --git a/lib/vquic/vquic.c b/lib/vquic/vquic.c index 087074d20b47..a47a78e9f287 100644 --- a/lib/vquic/vquic.c +++ b/lib/vquic/vquic.c @@ -1061,7 +1061,7 @@ CURLcode Curl_qlogdir(struct Curl_easy *data, #ifdef _WIN32 data->set.new_file_perms & (_S_IREAD | _S_IWRITE) #else - CURLX_FILE_MODE(data->set.new_file_perms) + (mode_t)data->set.new_file_perms #endif ); if(qlogfd != -1) From 8fa50e09ac848f85cba0fdc3e1a8f0864d3fa974 Mon Sep 17 00:00:00 2001 From: Viktor Szakats Date: Thu, 1 Oct 2026 10:04:03 +0200 Subject: [PATCH 344/481] GHA/linux: bump recent Alpine container to latest Also tried: - switching to an arm container running in an arm runner, but not supported by GitHub it seems. - bumping 3.20 to 3.21, but it failed with a node error in the GitHub checkout action, with both Ubuntu 24 and 26. No idea. ``` Run actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 /usr/bin/docker exec e59fd391d564a2d775a3d696d57a052590549a5c47420b84a5c45a1b2d189641 sh -c "cat /etc/*release | grep ^ID" exec /__e/node24_alpine/bin/node: no such file or directory ``` Ref: https://github.com/curl/curl/actions/runs/36835925602/job/110283424025?pr=23207#step:38:38 Closes #23207 --- .github/workflows/linux.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index e619a5768fd9..debfe3d5ef01 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -475,7 +475,7 @@ jobs: - name: 'Alpine MUSL https-rr' configure: --enable-debug --with-ssl --with-libssh2 --with-libidn2 --with-gssapi --enable-ldap --with-libpsl --enable-httpsrr --enable-ares --enable-threaded-resolver - container: alpine:3.23@sha256:5b10f432ef3da1b8d4c7eb6c487f2f5a8f096bc91145e68878dd4a5019afde11 # 3.23.4 + container: alpine:3.24@sha256:294b683cb724975bec92580e1e685676bd4b50bda910ddb8c51d4cabeaec77e6 # 3.24.2 - name: 'Alpine MUSL https-rr c-ares' configure: --enable-debug --with-ssl --with-libssh2 --with-libidn2 --with-gssapi --enable-ldap --with-libpsl --enable-httpsrr --enable-ares --disable-threaded-resolver From 9be300cb5eadc16a432e321a76c8efc5250d53c4 Mon Sep 17 00:00:00 2001 From: Stefan Eissing Date: Thu, 1 Oct 2026 10:12:14 +0200 Subject: [PATCH 345/481] test/servers.pm: restrict h2/3 port reuse Tracked down the spurious failures with HTTP/2+3 with runtests.pl that show nghttpx refusing to start when the port was already in use. Add reuse flag to `getfreeport()` to mitigate this. Disallowing reuse for h2+3. Closes #23208 --- tests/servers.pm | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/tests/servers.pm b/tests/servers.pm index 479824031977..ebcacba239f6 100644 --- a/tests/servers.pm +++ b/tests/servers.pm @@ -175,11 +175,11 @@ sub checkcmd { # return the port number # sub getfreeport { - my ($ipnum) = @_; + my ($ipnum, $reuse) = @_; my $server = IO::Socket->new(LocalPort => 0, Domain => $ipnum == 6 ? AF_INET6 : AF_INET, Type => SOCK_STREAM, - Reuse => 1, + Reuse => $reuse, Listen => 10 ) or die "Could not create tcp server socket: $@\n"; @@ -1044,8 +1044,8 @@ sub runhttp2server { $flags .= "--connect $HOSTIP:" . protoport("http") . " "; $flags .= $verbose_flag if($debugprotocol); - my $port = getfreeport($ipvnum); - my $port2 = getfreeport($ipvnum); + my $port = getfreeport($ipvnum, 0); + my $port2 = getfreeport($ipvnum, 0); my $aflags = "--port $port --port2 $port2 $flags"; my $cmd = "$exe $aflags"; my ($http2pid, $pid2) = startnew($cmd, $pidfile, 15, 0); @@ -1106,7 +1106,7 @@ sub runhttp3server { $flags .= "--cert \"$cert\" " if($cert); $flags .= $verbose_flag if($debugprotocol); - my $port = getfreeport($ipvnum); + my $port = getfreeport($ipvnum, 0); my $aflags = "--port $port $flags"; my $cmd = "$exe $aflags"; my ($http3pid, $pid3) = startnew($cmd, $pidfile, 15, 0); @@ -1190,7 +1190,7 @@ sub runhttpsserver { $flags .= "--connect " . protoport("httpproxy"); } - my $port = getfreeport($ipvnum); + my $port = getfreeport($ipvnum, 1); my $options = "$flags --accept $port"; my $cmd = "$perl " . shell_quote("$srcdir/secureserver.pl") . " " . $options; my ($httpspid, $pid2) = startnew($cmd, $pidfile, 15, 0); @@ -1333,7 +1333,7 @@ sub runsecureserver { $flags .= "--stunnel \"$stunnel\" --srcdir \"$srcdir\" "; $flags .= "--connect $clearport"; - my $port = getfreeport($ipvnum); + my $port = getfreeport($ipvnum, 1); my $options = "$flags --accept $port"; my $cmd = "$perl " . shell_quote("$srcdir/secureserver.pl") . " " . $options; @@ -1636,7 +1636,7 @@ sub runsshserver { } my @tports; - my $port = getfreeport($ipvnum); + my $port = getfreeport($ipvnum, 1); push @tports, $port; @@ -1890,7 +1890,7 @@ sub rundictserver { $flags .= "--srcdir \"$srcdir\" "; $flags .= "--host $HOSTIP"; - my $port = getfreeport($ipvnum); + my $port = getfreeport($ipvnum, 1); my $aflags = "--port $port $flags"; my $cmd = "$srcdir/dictserver.py $aflags"; my ($dictpid, $pid2) = startnew($cmd, $pidfile, 15, 0); @@ -1950,7 +1950,7 @@ sub runnegtelnetserver { $flags .= "--id $idnum " if($idnum > 1); $flags .= "--srcdir \"$srcdir\""; - my $port = getfreeport($ipvnum); + my $port = getfreeport($ipvnum, 1); my $aflags = "--port $port $flags"; my $cmd = "$srcdir/negtelnetserver.py $aflags"; my ($ntelpid, $pid2) = startnew($cmd, $pidfile, 15, 0); From bc4abc781ca274d273e872d7fa9f7a34aed7e87c Mon Sep 17 00:00:00 2001 From: Viktor Szakats Date: Thu, 1 Oct 2026 10:51:21 +0200 Subject: [PATCH 346/481] GHA/linux: make Intel C build more interesting Enable idn, httpsig, krb5, ldap, libssh2, nghttp2. Number of runtests 1807 -> 2021. Closes #23209 --- .github/workflows/linux.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index debfe3d5ef01..df8b70f20252 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -35,7 +35,7 @@ cache-mode: write env: MAKEFLAGS: -j 5 CURL_CI: github - CURL_TEST_MIN: 1790 + CURL_TEST_MIN: 1800 DO_NOT_TRACK: '1' # renovate: datasource=github-tags depName=awslabs/aws-lc versioning=semver registryUrl=https://github.com AWSLC_VERSION: 5.8.0 @@ -460,10 +460,10 @@ jobs: tflags: '-n --test-duphandle' - name: 'IntelC openssl' - install_packages: libssl-dev + install_packages: libssl-dev libssh2-1-dev libnghttp2-dev libidn2-dev libldap-dev libkrb5-dev install_steps: intelc CC: icc - configure: --enable-debug --with-openssl + configure: --enable-debug --with-openssl --with-libssh2 --with-gssapi --enable-httpsig - name: 'Slackware GnuTLS gssapi gcc' # Flags used to build the curl Slackware package: From 01d64713539562997d71a8e34acd3d92a6e9c705 Mon Sep 17 00:00:00 2001 From: Viktor Szakats Date: Thu, 1 Oct 2026 11:56:50 +0200 Subject: [PATCH 347/481] runtests: add extra room for mininum number of tests in sliced jobs To avoid random fallouts and micro-management of limits. Fixing, seen in 'AM curl_global_init_mem debug valgrind 2': ``` TESTDONE: 899 tests out of 899 reported OK: 100% TESTFAIL: number of tests run (899) was below the minimum of: 900 ``` Ref: https://github.com/curl/curl/actions/runs/36842851539/job/110305788106?pr=23209 Also: drop manual workarounds from CI. Follow-up to 66486691ee1c5c768c74e402b8b95902b88d90fb #22619 Closes #23211 --- .github/workflows/linux.yml | 4 ++-- .github/workflows/windows.yml | 4 ++-- tests/runtests.pl | 2 ++ 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index df8b70f20252..bd4951a13d4b 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -179,7 +179,7 @@ jobs: install_packages: libnghttp2-dev libldap-dev valgrind install_steps: rust rustls-arm PKG_CONFIG_PATH: /home/runner/rustls/lib/pkgconfig - tflags: '--subset=0/2 --min=880' + tflags: '--subset=0/2' generate: -DCURL_USE_RUSTLS=ON -DUSE_ECH=ON -DENABLE_DEBUG=ON - name: 'rustls valgrind 2' @@ -207,7 +207,7 @@ jobs: image: ubuntu-26.04-arm install_packages: valgrind install_steps: wolfssl-opensslextra-arm - tflags: '--subset=0/2 --min=880' + tflags: '--subset=0/2' LDFLAGS: -Wl,-rpath,/home/runner/wolfssl-opensslextra/lib configure: --with-wolfssl=/home/runner/wolfssl-opensslextra --enable-ech --enable-debug --enable-httpsig diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 162bf6f629bd..230454a687c0 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -283,10 +283,10 @@ jobs: install: 'mingw-w64-clang-x86_64-python' } # for dict and telnet tests # MinGW torture - { name: 'schannel U torture 1', type: 'Debug', - build: 'cmake' , sys: 'ucrt64' , env: 'ucrt-x86_64' , tflags: '-t --shallow=13 --subset=0/2 --min=920', + build: 'cmake' , sys: 'ucrt64' , env: 'ucrt-x86_64' , tflags: '-t --shallow=13 --subset=0/2', config: '-DENABLE_DEBUG=ON -DBUILD_SHARED_LIBS=OFF -DCURL_USE_SCHANNEL=ON -DENABLE_UNICODE=ON -DENABLE_ARES=ON' } - { name: 'schannel U torture 2', type: 'Debug', - build: 'cmake' , sys: 'ucrt64' , env: 'ucrt-x86_64' , tflags: '-t --shallow=13 --subset=1/2 --min=900', + build: 'cmake' , sys: 'ucrt64' , env: 'ucrt-x86_64' , tflags: '-t --shallow=13 --subset=1/2', config: '-DENABLE_DEBUG=ON -DBUILD_SHARED_LIBS=OFF -DCURL_USE_SCHANNEL=ON -DENABLE_UNICODE=ON -DENABLE_ARES=ON' } # WARNING: libssh uses hard-coded world-writable paths (C:ProgramData/, /etc/..., ~/.ssh/) # to read its configuration from, making it vulnerable to attacks on diff --git a/tests/runtests.pl b/tests/runtests.pl index 12213ad12f97..76c0b180cd07 100755 --- a/tests/runtests.pl +++ b/tests/runtests.pl @@ -2739,6 +2739,8 @@ sub pickrunner { $mintotal = $ENV{"CURL_TEST_MIN"}; if($useshares) { $mintotal /= $useshares; + # add safety margin to avoid potential underruns + $mintotal = $mintotal - 30 < 1 ? 1 : $mintotal - 30; } } From f1b4d6c55c206ed332e64b8fde838b259f4e861d Mon Sep 17 00:00:00 2001 From: Viktor Szakats Date: Thu, 1 Oct 2026 13:41:46 +0200 Subject: [PATCH 348/481] GHA/http3-linux: replace URL using insecure redirects with secure alternative Fixing: ``` [...] "https://ftpmirror.gnu.org/nettle/nettle-${NETTLE_VERSION}.tar.gz" [...] curl: (1) Protocol "http" disabled (in redirect) ``` Ref: https://github.com/curl/curl/actions/runs/36856289932/job/110349436255?pr=23214#step:20:33 Follow-up to 500820682ce570f21586f567ddec4dbea4e6dad5 #21757 Cherry-picked from #23214 Closes #23217 --- .github/workflows/http3-linux.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/http3-linux.yml b/.github/workflows/http3-linux.yml index 16cbb57f84cc..2cc8657ef843 100644 --- a/.github/workflows/http3-linux.yml +++ b/.github/workflows/http3-linux.yml @@ -262,7 +262,7 @@ jobs: run: | cd ~ curl --disable --fail --silent --show-error --connect-timeout 15 --max-time 120 --retry 6 --retry-connrefused \ - --location --proto-redir =https "https://ftpmirror.gnu.org/nettle/nettle-${NETTLE_VERSION}.tar.gz" --output pkg.bin + --location --proto-redir =https "https://ftp.gnu.org/gnu/nettle/nettle-${NETTLE_VERSION}.tar.gz" --output pkg.bin sha256sum pkg.bin && tar -xzf pkg.bin && rm -f pkg.bin cd "nettle-${NETTLE_VERSION}" autoreconf -fi From ce420595b50af6941fe66ed74ec77ab3eafcbd5c Mon Sep 17 00:00:00 2001 From: Viktor Szakats Date: Thu, 1 Oct 2026 14:21:31 +0200 Subject: [PATCH 349/481] GHA/linux: fix misplaced Renovate annotation for nettle / gnutls Follow-up to 4041eea61edd8cf66c6f85c47a430bb5774f017c #19680 Closes #23218 --- .github/workflows/http3-linux.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/http3-linux.yml b/.github/workflows/http3-linux.yml index 2cc8657ef843..be23b96e075e 100644 --- a/.github/workflows/http3-linux.yml +++ b/.github/workflows/http3-linux.yml @@ -43,9 +43,9 @@ env: AWSLC_VERSION: 5.8.0 # renovate: datasource=github-tags depName=google/boringssl versioning=semver registryUrl=https://github.com BORINGSSL_VERSION: 0.20260929.0 - # renovate: datasource=github-tags depName=gnutls/nettle versioning=semver registryUrl=https://github.com + # renovate: datasource=github-tags depName=gnutls/nettle versioning=semver extractVersion=^nettle_?(?.+)_release_.+$ registryUrl=https://github.com NETTLE_VERSION: 3.10.2 - # renovate: datasource=github-tags depName=gnutls/gnutls versioning=semver extractVersion=^nettle_?(?.+)_release_.+$ registryUrl=https://github.com + # renovate: datasource=github-tags depName=gnutls/gnutls versioning=semver registryUrl=https://github.com GNUTLS_VERSION: 3.8.11 # renovate: datasource=github-tags depName=libressl/portable versioning=semver registryUrl=https://github.com LIBRESSL_VERSION: 4.3.2 From 5f3e12f04934fa22a8f6d7c49fe5387391c88888 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 1 Oct 2026 12:25:38 +0000 Subject: [PATCH 350/481] GHA: update dependency gnutls/gnutls to v3.8.13 Closes #23219 --- .github/workflows/http3-linux.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/http3-linux.yml b/.github/workflows/http3-linux.yml index be23b96e075e..87cf7f27ade2 100644 --- a/.github/workflows/http3-linux.yml +++ b/.github/workflows/http3-linux.yml @@ -46,7 +46,7 @@ env: # renovate: datasource=github-tags depName=gnutls/nettle versioning=semver extractVersion=^nettle_?(?.+)_release_.+$ registryUrl=https://github.com NETTLE_VERSION: 3.10.2 # renovate: datasource=github-tags depName=gnutls/gnutls versioning=semver registryUrl=https://github.com - GNUTLS_VERSION: 3.8.11 + GNUTLS_VERSION: 3.8.13 # renovate: datasource=github-tags depName=libressl/portable versioning=semver registryUrl=https://github.com LIBRESSL_VERSION: 4.3.2 # renovate: datasource=github-releases depName=openssl/openssl versioning=semver extractVersion=^openssl-(?.+)$ registryUrl=https://github.com From 1482b86871b92fb13cfc583a56062a2c5c6391e3 Mon Sep 17 00:00:00 2001 From: Viktor Szakats Date: Thu, 1 Oct 2026 14:50:08 +0200 Subject: [PATCH 351/481] GHA/http3-linux: try `semver-coerced` in Renovate nettle version annotation The version is not strict semver, e.g. now it's `4.0`, and Renovate does not pick it up. Retry with relaxed semver versioning. Ref: https://docs.renovatebot.com/modules/versioning/semver-coerced/ Follow-up to ce420595b50af6941fe66ed74ec77ab3eafcbd5c #23218 Follow-up to 4041eea61edd8cf66c6f85c47a430bb5774f017c #19680 Closes #23221 --- .github/workflows/http3-linux.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/http3-linux.yml b/.github/workflows/http3-linux.yml index 87cf7f27ade2..238c40edc58e 100644 --- a/.github/workflows/http3-linux.yml +++ b/.github/workflows/http3-linux.yml @@ -43,7 +43,7 @@ env: AWSLC_VERSION: 5.8.0 # renovate: datasource=github-tags depName=google/boringssl versioning=semver registryUrl=https://github.com BORINGSSL_VERSION: 0.20260929.0 - # renovate: datasource=github-tags depName=gnutls/nettle versioning=semver extractVersion=^nettle_?(?.+)_release_.+$ registryUrl=https://github.com + # renovate: datasource=github-tags depName=gnutls/nettle versioning=semver-coerced extractVersion=^nettle_?(?.+)_release_.+$ registryUrl=https://github.com NETTLE_VERSION: 3.10.2 # renovate: datasource=github-tags depName=gnutls/gnutls versioning=semver registryUrl=https://github.com GNUTLS_VERSION: 3.8.13 From d4dc7d16c40a0d9e70f7858e645c7ab3997a38b0 Mon Sep 17 00:00:00 2001 From: Viktor Szakats Date: Thu, 1 Oct 2026 14:47:50 +0200 Subject: [PATCH 352/481] GHA/http3-linux: bump to gnutls/nettle 4.0 - adjust nettle install paths (`lib64` -> `lib`). - disable NTLM for GnuTLS jobs. Not supported by nettle 4+. Adjust minimum required tests. - put `4.0` in quotes in YAML to avoid parsing it as `4`. Why Renovate bot does not do this, I do not understand. Also: - drop HTTP redirect for nettle download URL. - drop obsolete `--disable-guile` GnuTLS build option. Fixes #23222 Closes #23223 --- .github/workflows/http3-linux.yml | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/.github/workflows/http3-linux.yml b/.github/workflows/http3-linux.yml index 238c40edc58e..8f4df9ac57f9 100644 --- a/.github/workflows/http3-linux.yml +++ b/.github/workflows/http3-linux.yml @@ -44,7 +44,7 @@ env: # renovate: datasource=github-tags depName=google/boringssl versioning=semver registryUrl=https://github.com BORINGSSL_VERSION: 0.20260929.0 # renovate: datasource=github-tags depName=gnutls/nettle versioning=semver-coerced extractVersion=^nettle_?(?.+)_release_.+$ registryUrl=https://github.com - NETTLE_VERSION: 3.10.2 + NETTLE_VERSION: '4.0' # renovate: datasource=github-tags depName=gnutls/gnutls versioning=semver registryUrl=https://github.com GNUTLS_VERSION: 3.8.13 # renovate: datasource=github-tags depName=libressl/portable versioning=semver registryUrl=https://github.com @@ -262,7 +262,7 @@ jobs: run: | cd ~ curl --disable --fail --silent --show-error --connect-timeout 15 --max-time 120 --retry 6 --retry-connrefused \ - --location --proto-redir =https "https://ftp.gnu.org/gnu/nettle/nettle-${NETTLE_VERSION}.tar.gz" --output pkg.bin + "https://ftp.gnu.org/gnu/nettle/nettle-${NETTLE_VERSION}.tar.gz" --output pkg.bin sha256sum pkg.bin && tar -xzf pkg.bin && rm -f pkg.bin cd "nettle-${NETTLE_VERSION}" autoreconf -fi @@ -281,10 +281,10 @@ jobs: autoreconf -fi # required: libp11-kit-dev libev-dev autopoint bison gperf gtk-doc-tools libtasn1-bin ./configure --disable-dependency-tracking --prefix=/home/runner/gnutls/build \ - PKG_CONFIG_PATH=/home/runner/nettle/build/lib64/pkgconfig \ - LDFLAGS=-Wl,-rpath,/home/runner/nettle/build/lib64 \ + PKG_CONFIG_PATH=/home/runner/nettle/build/lib/pkgconfig \ + LDFLAGS=-Wl,-rpath,/home/runner/nettle/build/lib \ --with-included-libtasn1 --with-included-unistring \ - --disable-guile --disable-doc --disable-tests --disable-tools + --disable-doc --disable-tests --disable-tools make install - name: 'build libressl' @@ -359,7 +359,7 @@ jobs: make install make clean export PKG_CONFIG_PATH=/home/runner/openssl/build/lib/pkgconfig - PKG_CONFIG_PATH+=:/home/runner/nettle/build/lib64/pkgconfig + PKG_CONFIG_PATH+=:/home/runner/nettle/build/lib/pkgconfig PKG_CONFIG_PATH+=:/home/runner/gnutls/build/lib/pkgconfig PKG_CONFIG_PATH+=:/home/runner/wolfssl/build/lib/pkgconfig ./configure --disable-dependency-tracking --prefix="$PWD"/build --enable-lib-only \ @@ -469,23 +469,24 @@ jobs: - name: 'gnutls' install_packages: libp11-kit-dev libssh-dev install_steps: skipall - LDFLAGS: -Wl,-rpath,/home/runner/gnutls/build/lib -Wl,-rpath,/home/runner/nettle/build/lib64 -Wl,-rpath,/home/runner/ngtcp2/build/lib - PKG_CONFIG_PATH: /home/runner/nettle/build/lib64/pkgconfig:/home/runner/gnutls/build/lib/pkgconfig:/home/runner/nghttp3/build/lib/pkgconfig:/home/runner/nghttp2/build/lib/pkgconfig + LDFLAGS: -Wl,-rpath,/home/runner/gnutls/build/lib -Wl,-rpath,/home/runner/nettle/build/lib -Wl,-rpath,/home/runner/ngtcp2/build/lib + PKG_CONFIG_PATH: /home/runner/nettle/build/lib/pkgconfig:/home/runner/gnutls/build/lib/pkgconfig:/home/runner/nghttp3/build/lib/pkgconfig:/home/runner/nghttp2/build/lib/pkgconfig configure: >- - --with-gnutls=/home/runner/gnutls/build --with-ngtcp2=/home/runner/ngtcp2/build --with-libssh --enable-ssls-export --enable-httpsig + --with-gnutls=/home/runner/gnutls/build --with-ngtcp2=/home/runner/ngtcp2/build --with-libssh --enable-ssls-export --enable-httpsig --disable-ntlm - name: 'gnutls' + tflags: '--min=1900' install_packages: libp11-kit-dev libssh-dev LDFLAGS: -Wl,-rpath,/home/runner/gnutls/build/lib PKG_CONFIG_PATH: "\ - /home/runner/nettle/build/lib64/pkgconfig:\ + /home/runner/nettle/build/lib/pkgconfig:\ /home/runner/gnutls/build/lib/pkgconfig:\ /home/runner/nghttp3/build/lib/pkgconfig:\ /home/runner/ngtcp2/build/lib/pkgconfig:\ /home/runner/nghttp2/build/lib/pkgconfig" generate: >- -DCURL_USE_GNUTLS=ON -DUSE_NGTCP2=ON -DCURL_USE_LIBSSH=ON - -DCMAKE_UNITY_BUILD=ON -DCURL_DISABLE_HTTPSIG=OFF + -DCMAKE_UNITY_BUILD=ON -DCURL_DISABLE_HTTPSIG=OFF -DCURL_ENABLE_NTLM=OFF - name: 'libressl' install_steps: skipall From 4e11d82a975ad1f44bdc3749d77cd03124eb5490 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 1 Oct 2026 14:57:04 +0000 Subject: [PATCH 353/481] GHA: bump Actions and pips - update `github/codeql-action/analyze` from 4.37.8 to 4.38.2 - update `github/codeql-action/init` from 4.37.8 to 4.38.2 - updates `cryptography` from 50.0.0 to 50.0.1 - updates `filelock` from 3.32.4 to 3.32.7 - updates `ruff` from 0.16.7 to 0.16.9 - updates `websockets` from 17.0.1 to 17.1 Closes #23224 Closes #23225 --- .github/scripts/requirements.txt | 2 +- .github/workflows/codeql.yml | 8 ++++---- tests/http/requirements.txt | 6 +++--- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/scripts/requirements.txt b/.github/scripts/requirements.txt index 1eebdec71e41..45a0fca0c740 100644 --- a/.github/scripts/requirements.txt +++ b/.github/scripts/requirements.txt @@ -5,4 +5,4 @@ cmakelang==0.6.13 codespell==2.4.3 reuse==6.2.0 -ruff==0.16.7 +ruff==0.16.9 diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index b5afa5dc1846..7fc2a26b0b9a 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -52,13 +52,13 @@ jobs: persist-credentials: false - name: 'initialize' - uses: github/codeql-action/init@db488ddef3bf6cb639b32c2e9a7c0a7ea8271d28 # v4.37.8 + uses: github/codeql-action/init@2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2 # v4.38.2 with: languages: actions, python queries: security-extended - name: 'perform analysis' - uses: github/codeql-action/analyze@db488ddef3bf6cb639b32c2e9a7c0a7ea8271d28 # v4.37.8 + uses: github/codeql-action/analyze@2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2 # v4.38.2 c: if: ${{ github.repository_owner == 'curl' || github.event_name != 'schedule' }} @@ -91,7 +91,7 @@ jobs: - name: 'initialize' # https://github.com/github/codeql-action/blob/main/init/action.yml - uses: github/codeql-action/init@db488ddef3bf6cb639b32c2e9a7c0a7ea8271d28 # v4.37.8 + uses: github/codeql-action/init@2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2 # v4.38.2 with: languages: cpp build-mode: manual @@ -138,4 +138,4 @@ jobs: - name: 'perform analysis' # https://github.com/github/codeql-action/blob/main/analyze/action.yml - uses: github/codeql-action/analyze@db488ddef3bf6cb639b32c2e9a7c0a7ea8271d28 # v4.37.8 + uses: github/codeql-action/analyze@2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2 # v4.38.2 diff --git a/tests/http/requirements.txt b/tests/http/requirements.txt index cb577aaa4ed6..fd6d7624c078 100644 --- a/tests/http/requirements.txt +++ b/tests/http/requirements.txt @@ -2,9 +2,9 @@ # # SPDX-License-Identifier: curl -cryptography==50.0.0 -filelock==3.32.4 +cryptography==50.0.1 +filelock==3.32.7 psutil==7.2.2 pytest==9.1.1 pytest-xdist==3.8.0 -websockets==17.0.1 +websockets==17.1 From 4d36408a6bff8b24327f70a9a79adcab8061726c Mon Sep 17 00:00:00 2001 From: Viktor Szakats Date: Thu, 1 Oct 2026 21:06:46 +0200 Subject: [PATCH 354/481] GHA/http3-linux: fix missing nghttp2 in 'AM openssl-prev' job Also sync option order in CMake sibling. Possibly hinted by Copilot Bug: https://github.com/curl/curl/pull/23214#pullrequestreview-5383726429 Closes #23226 --- .github/workflows/http3-linux.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/http3-linux.yml b/.github/workflows/http3-linux.yml index 8f4df9ac57f9..6e3d01a5835b 100644 --- a/.github/workflows/http3-linux.yml +++ b/.github/workflows/http3-linux.yml @@ -524,7 +524,7 @@ jobs: PKG_CONFIG_PATH: "\ /home/runner/openssl-prev/build/lib/pkgconfig:\ /home/runner/nghttp3/build/lib/pkgconfig:\ - /home/runner/nghttp2-openssl-prev/build/lib/pkgconfig" + /home/runner/nghttp2/build/lib/pkgconfig" configure: >- --with-openssl=/home/runner/openssl-prev/build --with-ngtcp2=/home/runner/ngtcp2-openssl-prev/build --enable-ssls-export @@ -533,8 +533,8 @@ jobs: PKG_CONFIG_PATH: "\ /home/runner/openssl-prev/build/lib/pkgconfig:\ /home/runner/nghttp3/build/lib/pkgconfig:\ - /home/runner/ngtcp2-openssl-prev/build/lib/pkgconfig:\ - /home/runner/nghttp2/build/lib/pkgconfig" + /home/runner/nghttp2/build/lib/pkgconfig:\ + /home/runner/ngtcp2-openssl-prev/build/lib/pkgconfig" generate: >- -DOPENSSL_ROOT_DIR=/home/runner/openssl-prev/build -DUSE_NGTCP2=ON -DCURL_DISABLE_LDAP=ON -DCURL_DISABLE_HTTPSIG=OFF From 39ad9b0771708c5aff20663a7ef3704bc55510d7 Mon Sep 17 00:00:00 2001 From: Viktor Szakats Date: Thu, 1 Oct 2026 23:52:29 +0200 Subject: [PATCH 355/481] GHA/distcheck: switch MSYS2 integration tests to ucrt64 From mingw64, which is deprecated upstream. Closes #23227 --- .github/workflows/distcheck.yml | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/.github/workflows/distcheck.yml b/.github/workflows/distcheck.yml index 1897d255ef18..d9bf3ca0d58e 100644 --- a/.github/workflows/distcheck.yml +++ b/.github/workflows/distcheck.yml @@ -274,12 +274,14 @@ jobs: - uses: msys2/setup-msys2@ec48f7c5447b3140e2b088413ae3a55687bccb6e # v2.33.0 if: ${{ contains(matrix.image, 'windows') }} with: - msystem: mingw64 + msystem: ucrt64 release: false update: false cache: false path-type: inherit - install: perl mingw-w64-x86_64-zlib mingw-w64-x86_64-zstd mingw-w64-x86_64-libpsl mingw-w64-x86_64-libssh2 mingw-w64-x86_64-nghttp2 mingw-w64-x86_64-openssl + install: >- + perl mingw-w64-ucrt-x86_64-zlib mingw-w64-ucrt-x86_64-zstd mingw-w64-ucrt-x86_64-libpsl + mingw-w64-ucrt-x86_64-libssh2 mingw-w64-ucrt-x86_64-nghttp2 mingw-w64-ucrt-x86_64-openssl - name: 'install prereqs' if: ${{ !contains(matrix.image, 'windows') }} @@ -330,7 +332,7 @@ jobs: if: ${{ contains(matrix.image, 'windows') }} run: | export TEST_CMAKE_FLAGS_PROVIDER='-DCMAKE_FIND_PACKAGE_PREFER_CONFIG=ON -DCURL_ZSTD=OFF' - TEST_CMAKE_FLAGS_PROVIDER+=' -DNGHTTP2_INCLUDE_DIR=C:/msys64/mingw64/include -DNGHTTP2_LIBRARY=C:/msys64/mingw64/lib/libnghttp2.dll.a' + TEST_CMAKE_FLAGS_PROVIDER+=' -DNGHTTP2_INCLUDE_DIR=C:/msys64/ucrt64/include -DNGHTTP2_LIBRARY=C:/msys64/ucrt64/lib/libnghttp2.dll.a' export TEST_CMAKE_FLAGS_CONSUMER="${TEST_CMAKE_FLAGS_PROVIDER}" ./tests/cmake/test.sh find_package ${TESTOPTS} -DCURL_USE_OPENSSL=ON @@ -356,7 +358,7 @@ jobs: fi if [[ "${MATRIX_IMAGE}" = *'windows'* ]]; then export TEST_CMAKE_GENERATOR='MSYS Makefiles' - export TEST_CMAKE_FLAGS='-DCMAKE_C_COMPILER=x86_64-w64-mingw32-gcc -DOPENSSL_ROOT_DIR=C:/msys64/mingw64' + export TEST_CMAKE_FLAGS='-DCMAKE_C_COMPILER=x86_64-w64-mingw32-gcc -DOPENSSL_ROOT_DIR=C:/msys64/ucrt64' fi ./tests/cmake/test.sh add_subdirectory ${TESTOPTS} -DCURL_USE_OPENSSL=ON @@ -369,7 +371,7 @@ jobs: fi if [[ "${MATRIX_IMAGE}" = *'windows'* ]]; then export TEST_CMAKE_GENERATOR='MSYS Makefiles' - export TEST_CMAKE_FLAGS='-DCMAKE_C_COMPILER=x86_64-w64-mingw32-gcc -DOPENSSL_ROOT_DIR=C:/msys64/mingw64' + export TEST_CMAKE_FLAGS='-DCMAKE_C_COMPILER=x86_64-w64-mingw32-gcc -DOPENSSL_ROOT_DIR=C:/msys64/ucrt64' fi ./tests/cmake/test.sh find_package ${TESTOPTS} -DCURL_USE_OPENSSL=ON From a23a6003533f86e1948d302fd8dc12088600cd35 Mon Sep 17 00:00:00 2001 From: Filippo Tedeschi Date: Wed, 23 Sep 2026 09:41:16 +0200 Subject: [PATCH 356/481] ws: check pointer args in curl_ws_recv Return CURLE_BAD_FUNCTION_ARGUMENT when NULL has been passed as 'recv' or 'metap' to curl_ws_recv(), and initialize the output values only after validating pointers inside the API guard. Closes https://github.com/curl/curl/pull/23050 --- lib/ws.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/ws.c b/lib/ws.c index 3d20c5b4e2f5..305991f96a71 100644 --- a/lib/ws.c +++ b/lib/ws.c @@ -1596,19 +1596,20 @@ CURLcode curl_ws_recv(CURL *curl, void *buffer, struct Curl_eapi_guard guard; CURLcode result = CURLE_OK; - *recv = 0; - *metap = NULL; if(CURL_EAPI_ENTER(&guard, curl, ws_recv, &result)) { struct Curl_easy *data = curl; struct connectdata *conn; struct websocket *ws; struct ws_collect ctx; - if(buflen && !buffer) { + if(!recv || !metap || (buflen && !buffer)) { result = CURLE_BAD_FUNCTION_ARGUMENT; goto out; } + *recv = 0; + *metap = NULL; + conn = data->conn; if(!conn) { /* Unhappy hack with lifetimes of transfers and connection */ From 318bd978ea2d36ace36b42da2174b79c3abd89a3 Mon Sep 17 00:00:00 2001 From: Stefan Eissing Date: Wed, 30 Sep 2026 13:59:20 +0200 Subject: [PATCH 357/481] http2: init some callbacks for HTTP/2 push easy handles - Copy some options from the parent to the new handle for better compatibility with the previous behaviour. The push handle now inherits these options from the parent easy handle: CURLOPT_WRITEFUNCTION(3), CURLOPT_WRITEDATA(3), CURLOPT_HEADERFUNCTION(3), CURLOPT_HEADERDATA(3), CURLOPT_DEBUGFUNCTION(3), CURLOPT_DEBUGDATA(3), CURLOPT_PROGRESSFUNCTION(3), CURLOPT_XFERINFOFUNCTION(3), CURLOPT_PROGRESSDATA(3) and CURLOPT_XFERINFODATA(3). Background: HTTP/2 push easy handles used to inherit *all* options from the parent handle (via curl_easy_duphandle) which ran into trouble in various things that push handles should *not* inherit. 8.22 changed the duplication to a new easy handle, but forgot to copy over some callback functions and data that are essential for applications and can run into trouble when only partially overwritten by the push callback. Reported-by: JuanCarlosgg@users.noreply.github.com Fixes https://github.com/curl/curl/issues/23183 Closes https://github.com/curl/curl/pull/23187 --- docs/libcurl/opts/CURLMOPT_PUSHFUNCTION.md | 10 +++++++--- lib/http2.c | 12 ++++++++++++ 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/docs/libcurl/opts/CURLMOPT_PUSHFUNCTION.md b/docs/libcurl/opts/CURLMOPT_PUSHFUNCTION.md index 1336133cde0b..c9b587d91960 100644 --- a/docs/libcurl/opts/CURLMOPT_PUSHFUNCTION.md +++ b/docs/libcurl/opts/CURLMOPT_PUSHFUNCTION.md @@ -44,9 +44,13 @@ pushes are denied automatically. The callback gets its arguments like this: *parent* is the handle of the stream on which this push arrives. The new -handle has been duplicated from the parent, meaning that it has gotten all its -options inherited. It is then up to the application to alter any options if -desired. +handle has the following options duplicated from the parent: +CURLOPT_WRITEFUNCTION(3), CURLOPT_WRITEDATA(3), CURLOPT_HEADERFUNCTION(3), +CURLOPT_HEADERDATA(3), CURLOPT_DEBUGFUNCTION(3), CURLOPT_DEBUGDATA(3), +CURLOPT_PROGRESSFUNCTION(3), CURLOPT_XFERINFOFUNCTION(3), +CURLOPT_PROGRESSDATA(3) and CURLOPT_XFERINFODATA(3). + +It is then up to the application to alter/set any options if desired. *easy* is a newly created handle that represents this upcoming transfer. diff --git a/lib/http2.c b/lib/http2.c index 51af1c2c5b1e..a8ff15fb6771 100644 --- a/lib/http2.c +++ b/lib/http2.c @@ -708,6 +708,18 @@ static struct Curl_easy *h2_duphandle(struct Curl_cfilter *cf, second->state.weight = data->state.weight; if(data->share) (void)Curl_share_easy_link(second, data->share); + /* copy a range of options from the parent handle */ + second->set.fwrite_func = data->set.fwrite_func; + second->set.out = data->set.out; + second->set.fwrite_header = data->set.fwrite_header; + second->set.writeheader = data->set.writeheader; + second->set.fdebug = data->set.fdebug; + second->set.debugdata = data->set.debugdata; + second->set.fxferinfo = data->set.fxferinfo; + second->set.fprogress = data->set.fprogress; + second->set.progress_client = data->set.progress_client; + second->progress.hide = data->progress.hide; + second->progress.callback = data->progress.callback; } return second; } From 978cce12adb7251b48a9dfa03a817df83f15cfaf Mon Sep 17 00:00:00 2001 From: Marcel Raad Date: Wed, 30 Sep 2026 11:56:22 +0200 Subject: [PATCH 358/481] openssl: fix OpenSSL v4.1 no-deprecated build - Replace calls to ASN1_STRING_length with ASN1_STRING_get_length. - For versions of OpenSSL < 4.1, add a wrapper function ASN1_STRING_get_length that calls ASN1_STRING_length. `ASN1_STRING_length` has been replaced by `ASN1_STRING_get_length` in the OpenSSL v4.1 API, which now returns `size_t` instead of `int`. Closes https://github.com/curl/curl/pull/23215 --- lib/vtls/openssl.c | 49 ++++++++++++++++++++++++++++++---------------- 1 file changed, 32 insertions(+), 17 deletions(-) diff --git a/lib/vtls/openssl.c b/lib/vtls/openssl.c index 16fd9fe2bcdc..3183d1097057 100644 --- a/lib/vtls/openssl.c +++ b/lib/vtls/openssl.c @@ -172,6 +172,14 @@ typedef unsigned long sslerr_t; #endif #define ossl_valsize_t numcert_t +#if OPENSSL_VERSION_NUMBER < 0x40100000L +static size_t ASN1_STRING_get_length(const ASN1_STRING *str) +{ + const int length = ASN1_STRING_length(str); + return length >= 0 ? (size_t)length : 0; +} +#endif + static CURLcode push_certinfo(struct Curl_easy *data, BIO *mem, const char *label, int num) WARN_UNUSED_RESULT; @@ -398,11 +406,9 @@ static CURLcode ossl_certchain(struct Curl_easy *data, SSL *ssl) result = CURLE_OUT_OF_MEMORY; for(i = 0; !result && (i < (int)numcerts); i++) { - ASN1_INTEGER *num; - const unsigned char *numdata; X509 *x = sk_X509_value(sk, (ossl_valsize_t)i); EVP_PKEY *pubkey = NULL; - int j; + size_t j; const ASN1_BIT_STRING *psig = NULL; X509_NAME_print_ex(mem, X509_get_subject_name(x), 0, XN_FLAG_ONELINE); @@ -420,12 +426,15 @@ static CURLcode ossl_certchain(struct Curl_easy *data, SSL *ssl) if(result) break; - num = X509_get_serialNumber(x); - if(ASN1_STRING_type(num) == V_ASN1_NEG_INTEGER) - BIO_puts(mem, "-"); - numdata = ASN1_STRING_get0_data(num); - for(j = 0; j < ASN1_STRING_length(num); j++) - BIO_printf(mem, "%02x", numdata[j]); + { + ASN1_INTEGER *num = X509_get_serialNumber(x); + const unsigned char *numdata = ASN1_STRING_get0_data(num); + const size_t numlen = ASN1_STRING_get_length(num); + if(ASN1_STRING_type(num) == V_ASN1_NEG_INTEGER) + BIO_puts(mem, "-"); + for(j = 0; j < numlen; j++) + BIO_printf(mem, "%02x", numdata[j]); + } result = push_certinfo(data, mem, "Serial Number", i); if(result) break; @@ -495,7 +504,8 @@ static CURLcode ossl_certchain(struct Curl_easy *data, SSL *ssl) if(!result && psig) { const unsigned char *psigdata = ASN1_STRING_get0_data(psig); - for(j = 0; j < ASN1_STRING_length(psig); j++) + const size_t psiglen = ASN1_STRING_get_length(psig); + for(j = 0; j < psiglen; j++) BIO_printf(mem, "%02x:", psigdata[j]); result = push_certinfo(data, mem, "Signature", i); } @@ -2085,7 +2095,7 @@ static CURLcode ossl_verifyhost(struct Curl_easy *data, if(check->type == target) { /* get data and length */ const char *altptr = (const char *)ASN1_STRING_get0_data(check->d.ia5); - size_t altlen = (size_t)ASN1_STRING_length(check->d.ia5); + size_t altlen = ASN1_STRING_get_length(check->d.ia5); switch(target) { case GEN_DNS: /* name/pattern comparison */ @@ -2131,7 +2141,7 @@ static CURLcode ossl_verifyhost(struct Curl_easy *data, distinguished one to get the most significant one. */ int i = -1; unsigned char *cn = NULL; - int cnlen = 0; + size_t cnlen = 0; bool free_cn = FALSE; /* The following is done because of a bug in 0.9.6b */ @@ -2156,15 +2166,19 @@ static CURLcode ossl_verifyhost(struct Curl_easy *data, conditional in the future when OpenSSL has been fixed. */ if(tmp) { if(ASN1_STRING_type(tmp) == V_ASN1_UTF8STRING) { - cnlen = ASN1_STRING_length(tmp); + cnlen = ASN1_STRING_get_length(tmp); cn = (unsigned char *)CURL_UNCONST(ASN1_STRING_get0_data(tmp)); } else { /* not a UTF8 name */ - cnlen = ASN1_STRING_to_UTF8(&cn, tmp); + const int utf8len = ASN1_STRING_to_UTF8(&cn, tmp); + if(utf8len < 0) + cnlen = 0; + else + cnlen = (size_t)utf8len; free_cn = TRUE; } - if((cnlen <= 0) || !cn) + if((cnlen == 0) || !cn) result = CURLE_OUT_OF_MEMORY; else if(memchr(cn, '\0', cnlen)) { /* there was a null-terminator before the end of string, this @@ -2185,11 +2199,12 @@ static CURLcode ossl_verifyhost(struct Curl_easy *data, else if(!Curl_cert_hostcheck((const char *)cn, cnlen, peer->origin->hostname, hostlen)) { failf(data, "SSL: certificate subject name '%.*s' does not match " - "target hostname '%s'", cnlen, cn, peer->origin->user_hostname); + "target hostname '%s'", (int)cnlen, cn, + peer->origin->user_hostname); result = CURLE_PEER_FAILED_VERIFICATION; } else { - infof(data, " common name: %.*s (matched)", cnlen, cn); + infof(data, " common name: %.*s (matched)", (int)cnlen, cn); } if(free_cn) OPENSSL_free(cn); From 26214024f595e785fc154b159d5f649dad04d6df Mon Sep 17 00:00:00 2001 From: Stefan Eissing Date: Thu, 1 Oct 2026 13:58:24 +0200 Subject: [PATCH 359/481] os400: use correct free function Replace use of `free(s)` with `untyped_free()` that uses the correct allocator. reported-by: Joshua Rogers Closes https://github.com/curl/curl/pull/23216 --- projects/OS400/ccsidcurl.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/projects/OS400/ccsidcurl.c b/projects/OS400/ccsidcurl.c index d5d705a7f8ea..2ee4d71eafc5 100644 --- a/projects/OS400/ccsidcurl.c +++ b/projects/OS400/ccsidcurl.c @@ -260,7 +260,7 @@ static struct curl_slist *slist_convert(struct curl_slist *from, nl = Curl_slist_append_nodup(to, s); if(!nl) { curl_slist_free_all(to); - free(s); + untyped_free(s); return NULL; } to = nl; From 7c732f5e81109c0f8e76b3a01b833ed27c7d7104 Mon Sep 17 00:00:00 2001 From: Viktor Szakats Date: Fri, 2 Oct 2026 09:41:27 +0200 Subject: [PATCH 360/481] GHA/distcheck: pass unix-style paths in MSYS2 configurations Closes #23229 --- .github/workflows/distcheck.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/distcheck.yml b/.github/workflows/distcheck.yml index d9bf3ca0d58e..a0f0f14ed9b6 100644 --- a/.github/workflows/distcheck.yml +++ b/.github/workflows/distcheck.yml @@ -332,7 +332,7 @@ jobs: if: ${{ contains(matrix.image, 'windows') }} run: | export TEST_CMAKE_FLAGS_PROVIDER='-DCMAKE_FIND_PACKAGE_PREFER_CONFIG=ON -DCURL_ZSTD=OFF' - TEST_CMAKE_FLAGS_PROVIDER+=' -DNGHTTP2_INCLUDE_DIR=C:/msys64/ucrt64/include -DNGHTTP2_LIBRARY=C:/msys64/ucrt64/lib/libnghttp2.dll.a' + TEST_CMAKE_FLAGS_PROVIDER+=' -DNGHTTP2_INCLUDE_DIR=/ucrt64/include -DNGHTTP2_LIBRARY=/ucrt64/lib/libnghttp2.dll.a' export TEST_CMAKE_FLAGS_CONSUMER="${TEST_CMAKE_FLAGS_PROVIDER}" ./tests/cmake/test.sh find_package ${TESTOPTS} -DCURL_USE_OPENSSL=ON @@ -358,7 +358,7 @@ jobs: fi if [[ "${MATRIX_IMAGE}" = *'windows'* ]]; then export TEST_CMAKE_GENERATOR='MSYS Makefiles' - export TEST_CMAKE_FLAGS='-DCMAKE_C_COMPILER=x86_64-w64-mingw32-gcc -DOPENSSL_ROOT_DIR=C:/msys64/ucrt64' + export TEST_CMAKE_FLAGS='-DCMAKE_C_COMPILER=x86_64-w64-mingw32-gcc -DOPENSSL_ROOT_DIR=/ucrt64' fi ./tests/cmake/test.sh add_subdirectory ${TESTOPTS} -DCURL_USE_OPENSSL=ON @@ -371,7 +371,7 @@ jobs: fi if [[ "${MATRIX_IMAGE}" = *'windows'* ]]; then export TEST_CMAKE_GENERATOR='MSYS Makefiles' - export TEST_CMAKE_FLAGS='-DCMAKE_C_COMPILER=x86_64-w64-mingw32-gcc -DOPENSSL_ROOT_DIR=C:/msys64/ucrt64' + export TEST_CMAKE_FLAGS='-DCMAKE_C_COMPILER=x86_64-w64-mingw32-gcc -DOPENSSL_ROOT_DIR=/ucrt64' fi ./tests/cmake/test.sh find_package ${TESTOPTS} -DCURL_USE_OPENSSL=ON From 66ea3f941ca5c614919e70fc3702847ab8f2dc8d Mon Sep 17 00:00:00 2001 From: Viktor Szakats Date: Fri, 2 Oct 2026 10:19:12 +0200 Subject: [PATCH 361/481] tests/cmake: suppress stderr in `if` expression Silencing, seen with MSYS2 shell: ``` + ln -s ../.. curl ln: failed to create symbolic link 'curl': Too many levels of symbolic links + rm -rf curl ``` Ref: https://github.com/curl/curl/actions/runs/36984847008/job/110767410137?pr=23230#step:13:45 Closes #23230 --- tests/cmake/test.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/cmake/test.sh b/tests/cmake/test.sh index b9bb1fc771e7..40be316000c8 100755 --- a/tests/cmake/test.sh +++ b/tests/cmake/test.sh @@ -57,7 +57,7 @@ fi if [ "${mode}" = 'all' ] || [ "${mode}" = 'add_subdirectory' ]; then rm -rf curl - if ! ln -s "${src}" curl; then + if ! ln -s "${src}" curl 2>/dev/null; then rm -rf curl; mkdir curl; (cd "${src}"; git archive --format=tar HEAD) | tar -x --directory=curl # for MSYS2/Cygwin fi bldc='bld-add_subdirectory' From d4602243601f4e0239900762cca61f4c8f97be49 Mon Sep 17 00:00:00 2001 From: Viktor Szakats Date: Fri, 2 Oct 2026 10:22:34 +0200 Subject: [PATCH 362/481] GHA/http3-linux: bump OpenSSL 3 to latest Closes #23231 --- .github/workflows/http3-linux.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/http3-linux.yml b/.github/workflows/http3-linux.yml index 6e3d01a5835b..5c73c3361e9e 100644 --- a/.github/workflows/http3-linux.yml +++ b/.github/workflows/http3-linux.yml @@ -52,8 +52,8 @@ env: # renovate: datasource=github-releases depName=openssl/openssl versioning=semver extractVersion=^openssl-(?.+)$ registryUrl=https://github.com OPENSSL_VERSION: 4.0.3 # manually bumped - OPENSSL_PREV_VERSION: 3.6.2 - OPENSSL_PREV_SHA256: aaf51a1fe064384f811daeaeb4ec4dce7340ec8bd893027eee676af31e83a04f + OPENSSL_PREV_VERSION: 3.6.5 + OPENSSL_PREV_SHA256: a2157c2830efdec3788939b00c9b0638306d3f0bbb76dc4832ee503bb397df98 # renovate: datasource=github-tags depName=cloudflare/quiche versioning=semver registryUrl=https://github.com QUICHE_VERSION: 0.30.0 # renovate: datasource=github-tags depName=wolfSSL/wolfssl versioning=semver extractVersion=^v?(?.+)-stable$ registryUrl=https://github.com From 7f147c6f1d24cd9d52ca77586540ca4d58903cb6 Mon Sep 17 00:00:00 2001 From: Viktor Szakats Date: Fri, 2 Oct 2026 09:03:27 +0200 Subject: [PATCH 363/481] renovate: move configuration file to .github subdir To be next to its Dependabot sibling, and to declutter the root dir. Ref: https://docs.renovatebot.com/configuration-options/ Closes #23228 --- renovate.json => .github/renovate.json | 0 .github/scripts/distfiles.sh | 1 - REUSE.toml | 2 +- scripts/spacecheck.pl | 4 ++-- 4 files changed, 3 insertions(+), 4 deletions(-) rename renovate.json => .github/renovate.json (100%) diff --git a/renovate.json b/.github/renovate.json similarity index 100% rename from renovate.json rename to .github/renovate.json diff --git a/.github/scripts/distfiles.sh b/.github/scripts/distfiles.sh index 1fb7d7e31034..963258409798 100755 --- a/.github/scripts/distfiles.sh +++ b/.github/scripts/distfiles.sh @@ -15,7 +15,6 @@ gitonly=".git* ^appveyor.* ^GIT-INFO.md ^README.md -^renovate.json ^REUSE.toml ^SECURITY.md ^LICENSES/* diff --git a/REUSE.toml b/REUSE.toml index 9e6d2d166b44..6cd00845a7a9 100644 --- a/REUSE.toml +++ b/REUSE.toml @@ -14,6 +14,7 @@ SPDX-PackageDownloadLocation = "https://curl.se/" [[annotations]] path = [ ".github/pull_request_template.md", + ".github/renovate.json", "docs/INSTALL", "docs/libcurl/symbols-in-versions", "docs/options-in-versions", @@ -29,7 +30,6 @@ path = [ "projects/Windows/**", "README", "RELEASE-NOTES", - "renovate.json", "tests/certs/**", "tests/data/data**", "tests/data/test**", diff --git a/scripts/spacecheck.pl b/scripts/spacecheck.pl index 44274d0a21b4..4177a7a2be57 100755 --- a/scripts/spacecheck.pl +++ b/scripts/spacecheck.pl @@ -50,8 +50,8 @@ ); my @longline = ( - '\.github/workflows/windows\.yml$', - '^renovate\.json$', + '^\.github/renovate\.json$', + '^\.github/workflows/windows\.yml$', '^docs/DISTROS\.md$', '^projects/Windows/tmpl/.+\.vcxproj$', '^tests/data/test', From c431936d5b7452061412b10d7dce5f38fe489db3 Mon Sep 17 00:00:00 2001 From: Viktor Szakats Date: Fri, 2 Oct 2026 12:23:47 +0200 Subject: [PATCH 364/481] GHA/linux: move one Linux clang-tidy job to arm64 For a 10-100% speed improvement and more efficiency. 4m40s, 5m56s -> 3m48s, 3m52s: Before: https://github.com/curl/curl/actions/runs/36986432086/job/110772412853 https://github.com/curl/curl/actions/runs/36935550184/job/110614834919 After: https://github.com/curl/curl/actions/runs/36995273458/job/110800454074?pr=23232 https://github.com/curl/curl/actions/runs/36995975066/job/110802732827?pr=23232 Closes #23232 --- .github/workflows/linux.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index bd4951a13d4b..c5b9e98df3a9 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -363,7 +363,7 @@ jobs: -DCURL_CLANG_TIDY=ON -DCLANG_TIDY=/usr/bin/clang-tidy-22 - name: 'clang-tidy H3 c-ares !examples' - image: ubuntu-26.04 + image: ubuntu-26.04-arm install_packages: clang-22 clang-tidy-22 libidn2-dev libssh-dev libnghttp2-dev install_steps: skiprun install_steps_brew: libngtcp2 libnghttp3 c-ares From 9fd900ca54378cba4102dfb9fcb2d0a6428a50dc Mon Sep 17 00:00:00 2001 From: Viktor Szakats Date: Fri, 2 Oct 2026 13:15:21 +0200 Subject: [PATCH 365/481] GHA/linux: use automatic CPU arch for cached artifacts, related improvements To make it easy to switch runner machine and have the locally built dependencies and their caches adapt automatically. Also to drop maintaining local build logic for CPUs separately. Also: - add support for Fil-C on ARM64. - add a revision number ('1') to the cache keys and IDs. To make it easy to bump when doing modifications like this, or want to make tests that need rebuilding them. Cherry-picked from #23214 - move local builds from `~/` to `~/pkg/`. To have them under a single root, and to eventually sync them with GHA/http3-linux. Except for Rustls and Fil-C, which are binary downloads. Cherry-picked from #23214 Closes #23233 --- .github/workflows/linux.yml | 348 +++++++++++++++--------------------- 1 file changed, 144 insertions(+), 204 deletions(-) diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index c5b9e98df3a9..9bf4beb7b284 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -77,55 +77,55 @@ jobs: build: - name: 'awslc' install_steps: awslc pytest test-linter - LDFLAGS: -Wl,-rpath,/home/runner/awslc/lib - configure: --with-openssl=/home/runner/awslc --enable-ech --enable-ntlm + LDFLAGS: -Wl,-rpath,/home/runner/pkg/awslc/lib + configure: --with-openssl=/home/runner/pkg/awslc --enable-ech --enable-ntlm - name: 'awslc' install_packages: libidn2-dev install_steps: awslc clean - generate: -DOPENSSL_ROOT_DIR=/home/runner/awslc -DUSE_ECH=ON -DCMAKE_UNITY_BUILD=OFF -DCURL_DROP_UNUSED=ON -DCURL_PATCHSTAMP=test-patch -DCURL_ENABLE_NTLM=ON + generate: -DOPENSSL_ROOT_DIR=/home/runner/pkg/awslc -DUSE_ECH=ON -DCMAKE_UNITY_BUILD=OFF -DCURL_DROP_UNUSED=ON -DCURL_PATCHSTAMP=test-patch -DCURL_ENABLE_NTLM=ON - name: 'boringssl' install_steps: boringssl pytest - generate: -DOPENSSL_ROOT_DIR=/home/runner/boringssl -DOPENSSL_USE_STATIC_LIBS=ON -DBUILD_SHARED_LIBS=OFF -DUSE_ECH=ON -DCURL_ENABLE_NTLM=ON + generate: -DOPENSSL_ROOT_DIR=/home/runner/pkg/boringssl -DOPENSSL_USE_STATIC_LIBS=ON -DBUILD_SHARED_LIBS=OFF -DUSE_ECH=ON -DCURL_ENABLE_NTLM=ON - name: 'libressl krb5' image: ubuntu-24.04-arm install_packages: libidn2-dev libnghttp2-dev libldap-dev libkrb5-dev - install_steps: libressl-c-arm pytest codeset-test1 - LDFLAGS: -Wl,-rpath,/home/runner/libressl/lib - configure: --with-openssl=/home/runner/libressl --with-gssapi --enable-debug + install_steps: libressl-c pytest codeset-test1 + LDFLAGS: -Wl,-rpath,/home/runner/pkg/libressl/lib + configure: --with-openssl=/home/runner/pkg/libressl --with-gssapi --enable-debug - name: 'libressl krb5 valgrind 1' image: ubuntu-24.04-arm install_packages: libnghttp2-dev libldap-dev libkrb5-dev valgrind - install_steps: libressl-c-arm + install_steps: libressl-c tflags: '--subset=0/2' - generate: -DOPENSSL_ROOT_DIR=/home/runner/libressl -DCURL_USE_GSSAPI=ON -DENABLE_DEBUG=ON -DCURL_LIBCURL_VERSIONED_SYMBOLS=ON -DCURL_ENABLE_NTLM=ON + generate: -DOPENSSL_ROOT_DIR=/home/runner/pkg/libressl -DCURL_USE_GSSAPI=ON -DENABLE_DEBUG=ON -DCURL_LIBCURL_VERSIONED_SYMBOLS=ON -DCURL_ENABLE_NTLM=ON - name: 'libressl krb5 valgrind 2' image: ubuntu-24.04-arm install_packages: libnghttp2-dev libldap-dev libkrb5-dev valgrind - install_steps: libressl-c-arm + install_steps: libressl-c tflags: '--subset=1/2' - generate: -DOPENSSL_ROOT_DIR=/home/runner/libressl -DCURL_USE_GSSAPI=ON -DENABLE_DEBUG=ON -DCURL_LIBCURL_VERSIONED_SYMBOLS=ON -DCURL_ENABLE_NTLM=ON + generate: -DOPENSSL_ROOT_DIR=/home/runner/pkg/libressl -DCURL_USE_GSSAPI=ON -DENABLE_DEBUG=ON -DCURL_LIBCURL_VERSIONED_SYMBOLS=ON -DCURL_ENABLE_NTLM=ON - name: 'libressl clang' image: ubuntu-24.04-arm install_packages: clang - install_steps: libressl-c-arm + install_steps: libressl-c CC: clang - LDFLAGS: -Wl,-rpath,/home/runner/libressl/lib - configure: --with-openssl=/home/runner/libressl --enable-debug + LDFLAGS: -Wl,-rpath,/home/runner/pkg/libressl/lib + configure: --with-openssl=/home/runner/pkg/libressl --enable-debug - name: 'libressl Fil-C' install_steps: filc libressl-filc nghttp2-filc pytest tflags: '!776' # adds 1-9 minutes to the test run step, and fails consistently CC: /home/runner/filc/build/bin/filcc - PKG_CONFIG_PATH: /home/runner/nghttp2/lib/pkgconfig + PKG_CONFIG_PATH: /home/runner/pkg/nghttp2/lib/pkgconfig generate: >- -DBUILD_STATIC_LIBS=ON -DBUILD_SHARED_LIBS=OFF -DCMAKE_UNITY_BUILD=OFF -DCURL_DISABLE_TYPECHECK=ON - -DOPENSSL_ROOT_DIR=/home/runner/libressl -DCURL_USE_LIBPSL=OFF + -DOPENSSL_ROOT_DIR=/home/runner/pkg/libressl -DCURL_USE_LIBPSL=OFF -DCURL_ZLIB=OFF -DCURL_BROTLI=OFF -DCURL_ZSTD=OFF -DCURL_DISABLE_LDAP=ON -DUSE_LIBIDN2=OFF -DCURL_USE_LIBSSH2=OFF -DCURL_ENABLE_NTLM=ON @@ -133,43 +133,43 @@ jobs: - name: 'mbedtls gss valgrind 1' image: ubuntu-24.04-arm install_packages: libnghttp2-dev libidn2-dev libldap-dev libgss-dev valgrind - install_steps: mbedtls-latest-arm + install_steps: mbedtls-latest tflags: '--subset=0/2' - LDFLAGS: -Wl,-rpath,/home/runner/mbedtls/lib - PKG_CONFIG_PATH: /home/runner/mbedtls/lib/pkgconfig + LDFLAGS: -Wl,-rpath,/home/runner/pkg/mbedtls/lib + PKG_CONFIG_PATH: /home/runner/pkg/mbedtls/lib/pkgconfig generate: -DCURL_USE_MBEDTLS=ON -DENABLE_DEBUG=ON -DCURL_USE_GSSAPI=ON -DCURL_DROP_UNUSED=ON - name: 'mbedtls gss valgrind 2' image: ubuntu-24.04-arm install_packages: libnghttp2-dev libidn2-dev libldap-dev libgss-dev valgrind - install_steps: mbedtls-latest-arm + install_steps: mbedtls-latest tflags: '--subset=1/2' - LDFLAGS: -Wl,-rpath,/home/runner/mbedtls/lib - PKG_CONFIG_PATH: /home/runner/mbedtls/lib/pkgconfig + LDFLAGS: -Wl,-rpath,/home/runner/pkg/mbedtls/lib + PKG_CONFIG_PATH: /home/runner/pkg/mbedtls/lib/pkgconfig generate: -DCURL_USE_MBEDTLS=ON -DENABLE_DEBUG=ON -DCURL_USE_GSSAPI=ON - name: 'mbedtls clang' install_packages: libssh-dev libnghttp2-dev libldap-dev clang - install_steps: mbedtls-latest-intel pytest + install_steps: mbedtls-latest pytest CC: clang - LDFLAGS: -Wl,-rpath,/home/runner/mbedtls/lib - configure: --with-mbedtls=/home/runner/mbedtls --with-libssh --enable-debug --with-fish-functions-dir --with-zsh-functions-dir + LDFLAGS: -Wl,-rpath,/home/runner/pkg/mbedtls/lib + configure: --with-mbedtls=/home/runner/pkg/mbedtls --with-libssh --enable-debug --with-fish-functions-dir --with-zsh-functions-dir - name: 'mbedtls-prev' install_packages: libssh2-1-dev libnghttp2-dev libuv1-dev install_steps: mbedtls-prev pytest - PKG_CONFIG_PATH: /home/runner/mbedtls-prev/lib/pkgconfig # Requires v3.6.0 + PKG_CONFIG_PATH: /home/runner/pkg/mbedtls-prev/lib/pkgconfig # Requires v3.6.0 generate: -DCURL_USE_MBEDTLS=ON -DCURL_USE_LIBUV=ON -DENABLE_DEBUG=ON - name: 'mbedtls-pkg MultiSSL !pc' install_packages: libnghttp2-dev libmbedtls-dev - install_steps: mbedtls-latest-intel skipall + install_steps: mbedtls-latest skipall generate: >- -DCURL_USE_MBEDTLS=ON -DENABLE_DEBUG=ON -DCURL_DEFAULT_SSL_BACKEND=mbedtls - -DMBEDTLS_INCLUDE_DIR=/home/runner/mbedtls/include - -DMBEDTLS_LIBRARY=/home/runner/mbedtls/lib/libmbedtls.a - -DMBEDX509_LIBRARY=/home/runner/mbedtls/lib/libmbedx509.a - -DMBEDCRYPTO_LIBRARY=/home/runner/mbedtls/lib/libmbedcrypto.a + -DMBEDTLS_INCLUDE_DIR=/home/runner/pkg/mbedtls/include + -DMBEDTLS_LIBRARY=/home/runner/pkg/mbedtls/lib/libmbedtls.a + -DMBEDX509_LIBRARY=/home/runner/pkg/mbedtls/lib/libmbedx509.a + -DMBEDCRYPTO_LIBRARY=/home/runner/pkg/mbedtls/lib/libmbedcrypto.a -DCURL_USE_PKGCONFIG=OFF -DCURL_USE_OPENSSL=ON -DBUILD_LIBCURL_DOCS=OFF -DBUILD_MISC_DOCS=OFF -DENABLE_CURL_MANUAL=OFF -DCURL_COMPLETION_FISH=ON -DCURL_COMPLETION_ZSH=ON @@ -177,7 +177,7 @@ jobs: - name: 'rustls valgrind 1' image: ubuntu-24.04-arm install_packages: libnghttp2-dev libldap-dev valgrind - install_steps: rust rustls-arm + install_steps: rust rustls PKG_CONFIG_PATH: /home/runner/rustls/lib/pkgconfig tflags: '--subset=0/2' generate: -DCURL_USE_RUSTLS=ON -DUSE_ECH=ON -DENABLE_DEBUG=ON @@ -185,39 +185,39 @@ jobs: - name: 'rustls valgrind 2' image: ubuntu-24.04-arm install_packages: libnghttp2-dev libldap-dev valgrind - install_steps: rust rustls-arm + install_steps: rust rustls PKG_CONFIG_PATH: /home/runner/rustls/lib/pkgconfig tflags: '--subset=1/2' generate: -DCURL_USE_RUSTLS=ON -DUSE_ECH=ON -DENABLE_DEBUG=ON - name: 'rustls' install_packages: libnghttp2-dev libldap-dev - install_steps: rust rustls-intel skiprun pytest + install_steps: rust rustls skiprun pytest LDFLAGS: -Wl,-rpath,/home/runner/rustls/lib PKG_CONFIG_PATH: /home/runner/rustls/lib/pkgconfig configure: --with-rustls --enable-ech --enable-debug - name: 'wolfssl-all' image: ubuntu-26.04-arm - install_steps: wolfssl-all-arm - LDFLAGS: -Wl,-rpath,/home/runner/wolfssl-all/lib - configure: --with-wolfssl=/home/runner/wolfssl-all --enable-ech --enable-debug + install_steps: wolfssl-all + LDFLAGS: -Wl,-rpath,/home/runner/pkg/wolfssl-all/lib + configure: --with-wolfssl=/home/runner/pkg/wolfssl-all --enable-ech --enable-debug - name: 'wolfssl-opensslextra valgrind 1' image: ubuntu-26.04-arm install_packages: valgrind - install_steps: wolfssl-opensslextra-arm + install_steps: wolfssl-opensslextra tflags: '--subset=0/2' - LDFLAGS: -Wl,-rpath,/home/runner/wolfssl-opensslextra/lib - configure: --with-wolfssl=/home/runner/wolfssl-opensslextra --enable-ech --enable-debug --enable-httpsig + LDFLAGS: -Wl,-rpath,/home/runner/pkg/wolfssl-opensslextra/lib + configure: --with-wolfssl=/home/runner/pkg/wolfssl-opensslextra --enable-ech --enable-debug --enable-httpsig - name: 'wolfssl-opensslextra valgrind 2' image: ubuntu-26.04-arm install_packages: valgrind - install_steps: wolfssl-opensslextra-arm + install_steps: wolfssl-opensslextra tflags: '--subset=1/2' - LDFLAGS: -Wl,-rpath,/home/runner/wolfssl-opensslextra/lib - configure: --with-wolfssl=/home/runner/wolfssl-opensslextra --enable-ech --enable-debug --enable-httpsig + LDFLAGS: -Wl,-rpath,/home/runner/pkg/wolfssl-opensslextra/lib + configure: --with-wolfssl=/home/runner/pkg/wolfssl-opensslextra --enable-ech --enable-debug --enable-httpsig - name: 'openssl default' image: ubuntu-24.04 @@ -271,7 +271,7 @@ jobs: CC: clang configure: >- --enable-static --disable-shared --with-openssl --with-gssapi --enable-debug --disable-docs - --disable-manual --with-ldap=/home/runner/openldap-static --with-ldap-lib=ldap --with-lber-lib=lber + --disable-manual --with-ldap=/home/runner/pkg/openldap-static --with-ldap-lib=ldap --with-lber-lib=lber - name: 'openssl clang krb5 LTO' image: ubuntu-24.04-arm @@ -339,20 +339,20 @@ jobs: - name: 'clang-tidy' image: ubuntu-26.04 install_packages: clang-22 clang-tidy-22 libssl-dev libidn2-dev libssh2-1-dev libnghttp2-dev libldap-dev libkrb5-dev libgnutls28-dev - install_steps: skiprun mbedtls-latest-intel rustls-intel wolfssl-opensslextra-intel + install_steps: skiprun mbedtls-latest rustls wolfssl-opensslextra install_steps_brew: openssl@4 gsasl CC: clang-22 CFLAGS: -Wunused-macros LDFLAGS: >- - -Wl,-rpath,/home/runner/wolfssl-opensslextra/lib - -Wl,-rpath,/home/runner/mbedtls/lib + -Wl,-rpath,/home/runner/pkg/wolfssl-opensslextra/lib + -Wl,-rpath,/home/runner/pkg/mbedtls/lib -Wl,-rpath,/home/runner/rustls/lib -Wl,-rpath,/home/linuxbrew/.linuxbrew/opt/openssl@4/lib -Wl,-rpath,/home/linuxbrew/.linuxbrew/opt/gsasl/lib PKG_CONFIG_PATH: "\ - /home/runner/wolfssl-opensslextra/lib/pkgconfig:\ - /home/runner/mbedtls/lib/pkgconfig:\ + /home/runner/pkg/wolfssl-opensslextra/lib/pkgconfig:\ + /home/runner/pkg/mbedtls/lib/pkgconfig:\ /home/runner/rustls/lib/pkgconfig:\ /home/linuxbrew/.linuxbrew/opt/gsasl/lib/pkgconfig" generate: >- @@ -434,7 +434,7 @@ jobs: CC: clang-22 CFLAGS: -fsanitize=thread LDFLAGS: -fsanitize=thread - generate: -DCMAKE_BUILD_TYPE=Debug -DENABLE_DEBUG=ON -DOPENSSL_ROOT_DIR=/home/runner/openssl -DUSE_ECH=ON + generate: -DCMAKE_BUILD_TYPE=Debug -DENABLE_DEBUG=ON -DOPENSSL_ROOT_DIR=/home/runner/pkg/openssl -DUSE_ECH=ON - name: 'memory-sanitizer' image: ubuntu-26.04 @@ -517,51 +517,52 @@ jobs: if: ${{ contains(matrix.build.install_steps, 'filc') }} run: | cd /home/runner + arch="$(uname -m)" curl --disable --fail --silent --show-error --connect-timeout 15 --max-time 120 --retry 6 --retry-connrefused \ - --location --proto-redir =https "https://github.com/pizlonator/fil-c/releases/download/v${FIL_C_VERSION}/filc-${FIL_C_VERSION}-linux-x86_64.tar.xz" --output pkg.bin - sha256sum pkg.bin && tar -xJf pkg.bin && rm -f pkg.bin && mv "filc-${FIL_C_VERSION}-linux-x86_64" filc + --location --proto-redir =https "https://github.com/pizlonator/fil-c/releases/download/v${FIL_C_VERSION}/filc-${FIL_C_VERSION}-linux-${arch}.tar.xz" --output pkg.bin + sha256sum pkg.bin && tar -xJf pkg.bin && rm -f pkg.bin && mv "filc-${FIL_C_VERSION}-linux-${arch}" filc cd filc ./setup.sh - - name: 'cache libressl (c-arm)' - if: ${{ contains(matrix.build.install_steps, 'libressl-c-arm') }} + - name: 'cache libressl (c)' + if: ${{ contains(matrix.build.install_steps, 'libressl-c') }} uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 - id: cache-libressl-c-arm + id: cache1-libressl-c env: - cache-name: cache-libressl-c-arm + cache-name: cache1-libressl-c with: - path: ~/libressl - key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ env.LIBRESSL_VERSION }} + path: ~/pkg/libressl + key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ env.LIBRESSL_VERSION }}-${{ runner.arch }} - - name: 'build libressl (c-arm)' - if: ${{ contains(matrix.build.install_steps, 'libressl-c-arm') && !steps.cache-libressl-c-arm.outputs.cache-hit }} + - name: 'build libressl (c)' + if: ${{ contains(matrix.build.install_steps, 'libressl-c') && !steps.cache1-libressl-c.outputs.cache-hit }} run: | curl --disable --fail --silent --show-error --connect-timeout 15 --max-time 120 --retry 6 --retry-connrefused \ "https://ftp.openbsd.org/pub/OpenBSD/LibreSSL/libressl-${LIBRESSL_VERSION}.tar.gz" --output pkg.bin sha256sum pkg.bin && tar -xzf pkg.bin && rm -f pkg.bin cd "libressl-${LIBRESSL_VERSION}" - cmake -B . -G Ninja -DLIBRESSL_APPS=OFF -DLIBRESSL_TESTS=OFF -DCMAKE_INSTALL_PREFIX=/home/runner/libressl + cmake -B . -G Ninja -DLIBRESSL_APPS=OFF -DLIBRESSL_TESTS=OFF -DCMAKE_INSTALL_PREFIX=/home/runner/pkg/libressl cmake --build . cmake --install . - name: 'cache libressl (filc)' if: ${{ contains(matrix.build.install_steps, 'libressl-filc') }} uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 - id: cache-libressl-filc + id: cache1-libressl-filc env: - cache-name: cache-libressl-filc + cache-name: cache1-libressl-filc with: - path: ~/libressl - key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ env.LIBRESSL_VERSION }}-${{ env.FIL_C_VERSION }} + path: ~/pkg/libressl + key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ env.LIBRESSL_VERSION }}-${{ env.FIL_C_VERSION }}-${{ runner.arch }} - name: 'build libressl (filc)' - if: ${{ contains(matrix.build.install_steps, 'libressl-filc') && !steps.cache-libressl-filc.outputs.cache-hit }} + if: ${{ contains(matrix.build.install_steps, 'libressl-filc') && !steps.cache1-libressl-filc.outputs.cache-hit }} run: | curl --disable --fail --silent --show-error --connect-timeout 15 --max-time 120 --retry 6 --retry-connrefused \ "https://ftp.openbsd.org/pub/OpenBSD/LibreSSL/libressl-${LIBRESSL_VERSION}.tar.gz" --output pkg.bin sha256sum pkg.bin && tar -xzf pkg.bin && rm -f pkg.bin cd "libressl-${LIBRESSL_VERSION}" - cmake -B . -G Ninja -DLIBRESSL_APPS=OFF -DLIBRESSL_TESTS=OFF -DCMAKE_INSTALL_PREFIX=/home/runner/libressl \ + cmake -B . -G Ninja -DLIBRESSL_APPS=OFF -DLIBRESSL_TESTS=OFF -DCMAKE_INSTALL_PREFIX=/home/runner/pkg/libressl \ -DCMAKE_C_COMPILER=/home/runner/filc/build/bin/filcc -DENABLE_ASM=OFF cmake --build . cmake --install . @@ -569,131 +570,84 @@ jobs: - name: 'cache nghttp2 (filc)' if: ${{ contains(matrix.build.install_steps, 'nghttp2-filc') }} uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 - id: cache-nghttp2-filc + id: cache1-nghttp2-filc env: - cache-name: cache-nghttp2-filc + cache-name: cache1-nghttp2-filc with: - path: ~/nghttp2 - key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ env.NGHTTP2_VERSION }}-${{ env.FIL_C_VERSION }} + path: ~/pkg/nghttp2 + key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ env.NGHTTP2_VERSION }}-${{ env.FIL_C_VERSION }}-${{ runner.arch }} - name: 'build nghttp2 (filc)' - if: ${{ contains(matrix.build.install_steps, 'nghttp2-filc') && !steps.cache-nghttp2-filc.outputs.cache-hit }} + if: ${{ contains(matrix.build.install_steps, 'nghttp2-filc') && !steps.cache1-nghttp2-filc.outputs.cache-hit }} run: | curl --disable --fail --silent --show-error --connect-timeout 15 --max-time 120 --retry 6 --retry-connrefused \ --location --proto-redir =https "https://github.com/nghttp2/nghttp2/releases/download/v${NGHTTP2_VERSION}/nghttp2-${NGHTTP2_VERSION}.tar.xz" --output pkg.bin sha256sum pkg.bin && tar -xJf pkg.bin && rm -f pkg.bin cd "nghttp2-${NGHTTP2_VERSION}" - cmake -B . -G Ninja -DENABLE_LIB_ONLY=ON -DBUILD_TESTING=OFF -DENABLE_DOC=OFF -DCMAKE_INSTALL_PREFIX=/home/runner/nghttp2 \ + cmake -B . -G Ninja -DENABLE_LIB_ONLY=ON -DBUILD_TESTING=OFF -DENABLE_DOC=OFF -DCMAKE_INSTALL_PREFIX=/home/runner/pkg/nghttp2 \ -DBUILD_STATIC_LIBS=ON -DBUILD_SHARED_LIBS=OFF \ -DCMAKE_C_COMPILER=/home/runner/filc/build/bin/filcc cmake --build . cmake --install . - - name: 'cache wolfssl (all-arm)' - if: ${{ contains(matrix.build.install_steps, 'wolfssl-all-arm') }} + - name: 'cache wolfssl (all)' + if: ${{ contains(matrix.build.install_steps, 'wolfssl-all') }} uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 - id: cache-wolfssl-all-arm + id: cache1-wolfssl-all env: - cache-name: cache-wolfssl-all-arm + cache-name: cache1-wolfssl-all with: - path: ~/wolfssl-all - key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ env.WOLFSSL_VERSION }} + path: ~/pkg/wolfssl-all + key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ env.WOLFSSL_VERSION }}-${{ runner.arch }} - - name: 'build wolfssl (all-arm)' # does not support `OPENSSL_COEXIST` - if: ${{ contains(matrix.build.install_steps, 'wolfssl-all-arm') && !steps.cache-wolfssl-all-arm.outputs.cache-hit }} + - name: 'build wolfssl (all)' # does not support `OPENSSL_COEXIST` + if: ${{ contains(matrix.build.install_steps, 'wolfssl-all') && !steps.cache1-wolfssl-all.outputs.cache-hit }} run: | curl --disable --fail --silent --show-error --connect-timeout 15 --max-time 120 --retry 6 --retry-connrefused \ --location --proto-redir =https "https://github.com/wolfSSL/wolfssl/archive/v${WOLFSSL_VERSION}-stable.tar.gz" --output pkg.bin sha256sum pkg.bin && tar -xzf pkg.bin && rm -f pkg.bin cd "wolfssl-${WOLFSSL_VERSION}-stable" ./autogen.sh - ./configure --disable-dependency-tracking --prefix=/home/runner/wolfssl-all \ + ./configure --disable-dependency-tracking --prefix=/home/runner/pkg/wolfssl-all \ --enable-tls13 --enable-harden --enable-all \ --disable-benchmark --disable-crypttests --disable-examples make install - - name: 'cache wolfssl (opensslextra-intel)' # does support `OPENSSL_COEXIST` - if: ${{ contains(matrix.build.install_steps, 'wolfssl-opensslextra-intel') }} + - name: 'cache wolfssl (opensslextra)' # does support `OPENSSL_COEXIST` + if: ${{ contains(matrix.build.install_steps, 'wolfssl-opensslextra') }} uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 - id: cache-wolfssl-opensslextra-intel + id: cache1-wolfssl-opensslextra env: - cache-name: cache-wolfssl-opensslextra-intel + cache-name: cache1-wolfssl-opensslextra with: - path: ~/wolfssl-opensslextra - key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ env.WOLFSSL_VERSION }} + path: ~/pkg/wolfssl-opensslextra + key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ env.WOLFSSL_VERSION }}-${{ runner.arch }} - - name: 'build wolfssl (opensslextra-intel)' - if: ${{ contains(matrix.build.install_steps, 'wolfssl-opensslextra-intel') && !steps.cache-wolfssl-opensslextra-intel.outputs.cache-hit }} + - name: 'build wolfssl (opensslextra)' + if: ${{ contains(matrix.build.install_steps, 'wolfssl-opensslextra') && !steps.cache1-wolfssl-opensslextra.outputs.cache-hit }} run: | curl --disable --fail --silent --show-error --connect-timeout 15 --max-time 120 --retry 6 --retry-connrefused \ --location --proto-redir =https "https://github.com/wolfSSL/wolfssl/archive/v${WOLFSSL_VERSION}-stable.tar.gz" --output pkg.bin sha256sum pkg.bin && tar -xzf pkg.bin && rm -f pkg.bin cd "wolfssl-${WOLFSSL_VERSION}-stable" ./autogen.sh - ./configure --disable-dependency-tracking --prefix=/home/runner/wolfssl-opensslextra \ + ./configure --disable-dependency-tracking --prefix=/home/runner/pkg/wolfssl-opensslextra \ --enable-tls13 --enable-harden --enable-ech --enable-ed25519 --enable-opensslextra \ --disable-benchmark --disable-crypttests --disable-examples make install - - name: 'cache wolfssl (opensslextra-arm)' # does support `OPENSSL_COEXIST` - if: ${{ contains(matrix.build.install_steps, 'wolfssl-opensslextra-arm') }} + - name: 'cache mbedtls (latest)' + if: ${{ contains(matrix.build.install_steps, 'mbedtls-latest') }} uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 - id: cache-wolfssl-opensslextra-arm + id: cache1-mbedtls-latest env: - cache-name: cache-wolfssl-opensslextra-arm + cache-name: cache1-mbedtls-latest with: - path: ~/wolfssl-opensslextra - key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ env.WOLFSSL_VERSION }} + path: ~/pkg/mbedtls + key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ env.MBEDTLS_VERSION }}-${{ runner.arch }} - - name: 'build wolfssl (opensslextra-arm)' - if: ${{ contains(matrix.build.install_steps, 'wolfssl-opensslextra-arm') && !steps.cache-wolfssl-opensslextra-arm.outputs.cache-hit }} - run: | - curl --disable --fail --silent --show-error --connect-timeout 15 --max-time 120 --retry 6 --retry-connrefused \ - --location --proto-redir =https "https://github.com/wolfSSL/wolfssl/archive/v${WOLFSSL_VERSION}-stable.tar.gz" --output pkg.bin - sha256sum pkg.bin && tar -xzf pkg.bin && rm -f pkg.bin - cd "wolfssl-${WOLFSSL_VERSION}-stable" - ./autogen.sh - ./configure --disable-dependency-tracking --prefix=/home/runner/wolfssl-opensslextra \ - --enable-tls13 --enable-harden --enable-ech --enable-ed25519 --enable-opensslextra \ - --disable-benchmark --disable-crypttests --disable-examples - make install - - - name: 'cache mbedtls (latest-intel)' - if: ${{ contains(matrix.build.install_steps, 'mbedtls-latest-intel') }} - uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 - id: cache-mbedtls-latest-intel - env: - cache-name: cache-mbedtls-latest-intel - with: - path: ~/mbedtls - key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ env.MBEDTLS_VERSION }} - - - name: 'build mbedtls (latest-intel)' - if: ${{ contains(matrix.build.install_steps, 'mbedtls-latest-intel') && !steps.cache-mbedtls-latest-intel.outputs.cache-hit }} - run: | - curl --disable --fail --silent --show-error --connect-timeout 15 --max-time 120 --retry 6 --retry-connrefused \ - --location --proto-redir =https "https://github.com/Mbed-TLS/mbedtls/releases/download/mbedtls-${MBEDTLS_VERSION}/mbedtls-${MBEDTLS_VERSION}.tar.bz2" --output pkg.bin - sha256sum pkg.bin && tar -xjf pkg.bin && rm -f pkg.bin - cd "mbedtls-${MBEDTLS_VERSION}" - ./scripts/config.py set MBEDTLS_THREADING_C - ./scripts/config.py set MBEDTLS_THREADING_PTHREAD - cmake -B . -G Ninja -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_POSITION_INDEPENDENT_CODE=ON -DCMAKE_INSTALL_PREFIX=/home/runner/mbedtls \ - -DENABLE_PROGRAMS=OFF -DENABLE_TESTING=OFF - cmake --build . - cmake --install . - - - name: 'cache mbedtls (latest-arm)' - if: ${{ contains(matrix.build.install_steps, 'mbedtls-latest-arm') }} - uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 - id: cache-mbedtls-latest-arm - env: - cache-name: cache-mbedtls-latest-arm - with: - path: ~/mbedtls - key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ env.MBEDTLS_VERSION }} - - - name: 'build mbedtls (latest-arm)' - if: ${{ contains(matrix.build.install_steps, 'mbedtls-latest-arm') && !steps.cache-mbedtls-latest-arm.outputs.cache-hit }} + - name: 'build mbedtls (latest)' + if: ${{ contains(matrix.build.install_steps, 'mbedtls-latest') && !steps.cache1-mbedtls-latest.outputs.cache-hit }} run: | curl --disable --fail --silent --show-error --connect-timeout 15 --max-time 120 --retry 6 --retry-connrefused \ --location --proto-redir =https "https://github.com/Mbed-TLS/mbedtls/releases/download/mbedtls-${MBEDTLS_VERSION}/mbedtls-${MBEDTLS_VERSION}.tar.bz2" --output pkg.bin @@ -701,7 +655,7 @@ jobs: cd "mbedtls-${MBEDTLS_VERSION}" ./scripts/config.py set MBEDTLS_THREADING_C ./scripts/config.py set MBEDTLS_THREADING_PTHREAD - cmake -B . -G Ninja -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_POSITION_INDEPENDENT_CODE=ON -DCMAKE_INSTALL_PREFIX=/home/runner/mbedtls \ + cmake -B . -G Ninja -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_POSITION_INDEPENDENT_CODE=ON -DCMAKE_INSTALL_PREFIX=/home/runner/pkg/mbedtls \ -DENABLE_PROGRAMS=OFF -DENABLE_TESTING=OFF cmake --build . cmake --install . @@ -709,15 +663,15 @@ jobs: - name: 'cache mbedtls (prev)' if: ${{ contains(matrix.build.install_steps, 'mbedtls-prev') }} uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 - id: cache-mbedtls-prev + id: cache1-mbedtls-prev env: - cache-name: cache-mbedtls-prev + cache-name: cache1-mbedtls-prev with: - path: ~/mbedtls-prev - key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ env.MBEDTLS_PREV_VERSION }} + path: ~/pkg/mbedtls-prev + key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ env.MBEDTLS_PREV_VERSION }}-${{ runner.arch }} - name: 'build mbedtls (prev)' - if: ${{ contains(matrix.build.install_steps, 'mbedtls-prev') && !steps.cache-mbedtls-prev.outputs.cache-hit }} + if: ${{ contains(matrix.build.install_steps, 'mbedtls-prev') && !steps.cache1-mbedtls-prev.outputs.cache-hit }} run: | curl --disable --fail --silent --show-error --connect-timeout 15 --max-time 120 --retry 6 --retry-connrefused \ --location --proto-redir =https "https://github.com/Mbed-TLS/mbedtls/releases/download/mbedtls-${MBEDTLS_PREV_VERSION}/mbedtls-${MBEDTLS_PREV_VERSION}.tar.bz2" --output pkg.bin @@ -725,7 +679,7 @@ jobs: cd "mbedtls-${MBEDTLS_PREV_VERSION}" ./scripts/config.py set MBEDTLS_THREADING_C ./scripts/config.py set MBEDTLS_THREADING_PTHREAD - cmake -B . -G Ninja -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_POSITION_INDEPENDENT_CODE=ON -DCMAKE_INSTALL_PREFIX=/home/runner/mbedtls-prev \ + cmake -B . -G Ninja -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_POSITION_INDEPENDENT_CODE=ON -DCMAKE_INSTALL_PREFIX=/home/runner/pkg/mbedtls-prev \ -DENABLE_PROGRAMS=OFF -DENABLE_TESTING=OFF cmake --build . cmake --install . @@ -733,120 +687,106 @@ jobs: - name: 'cache openldap (static)' if: ${{ contains(matrix.build.install_steps, 'openldap-static') }} uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 - id: cache-openldap-static + id: cache1-openldap-static env: - cache-name: cache-openldap-static + cache-name: cache1-openldap-static with: - path: ~/openldap-static - key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ env.OPENLDAP_VERSION }} + path: ~/pkg/openldap-static + key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ env.OPENLDAP_VERSION }}-${{ runner.arch }} - name: 'build openldap (static)' - if: ${{ contains(matrix.build.install_steps, 'openldap-static') && !steps.cache-openldap-static.outputs.cache-hit }} + if: ${{ contains(matrix.build.install_steps, 'openldap-static') && !steps.cache1-openldap-static.outputs.cache-hit }} run: | curl --disable --fail --silent --show-error --connect-timeout 15 --max-time 120 --retry 6 --retry-connrefused \ "https://www.openldap.org/software/download/OpenLDAP/openldap-release/openldap-${OPENLDAP_VERSION}.tgz" --output pkg.bin sha256sum pkg.bin && tar -xzf pkg.bin && rm -f pkg.bin cd "openldap-${OPENLDAP_VERSION}" autoreconf -fi - ./configure --prefix=/home/runner/openldap-static --enable-static --disable-shared --disable-slapd + ./configure --prefix=/home/runner/pkg/openldap-static --enable-static --disable-shared --disable-slapd make install - name: 'cache openssl (thread sanitizer)' if: ${{ contains(matrix.build.install_steps, 'openssl-tsan') }} uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 - id: cache-openssl-tsan + id: cache1-openssl-tsan env: - cache-name: cache-openssl-tsan + cache-name: cache1-openssl-tsan with: - path: ~/openssl - key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ env.OPENSSL_VERSION }} + path: ~/pkg/openssl + key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ env.OPENSSL_VERSION }}-${{ runner.arch }} - name: 'build openssl (thread sanitizer)' - if: ${{ contains(matrix.build.install_steps, 'openssl-tsan') && !steps.cache-openssl-tsan.outputs.cache-hit }} + if: ${{ contains(matrix.build.install_steps, 'openssl-tsan') && !steps.cache1-openssl-tsan.outputs.cache-hit }} run: | git clone --quiet --depth 1 --branch "openssl-${OPENSSL_VERSION}" https://github.com/openssl/openssl cd openssl - CC=clang CFLAGS='-fsanitize=thread' LDFLAGS='-fsanitize=thread' ./config --prefix=/home/runner/openssl --libdir=lib no-makedepend no-apps no-docs no-tests + CC=clang CFLAGS='-fsanitize=thread' LDFLAGS='-fsanitize=thread' ./config --prefix=/home/runner/pkg/openssl --libdir=lib no-makedepend no-apps no-docs no-tests make make -j1 install_sw - name: 'cache awslc' if: ${{ contains(matrix.build.install_steps, 'awslc') }} uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 - id: cache-awslc + id: cache1-awslc env: - cache-name: cache-awslc + cache-name: cache1-awslc with: - path: ~/awslc - key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ env.AWSLC_VERSION }} + path: ~/pkg/awslc + key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ env.AWSLC_VERSION }}-${{ runner.arch }} - name: 'build awslc' - if: ${{ contains(matrix.build.install_steps, 'awslc') && !steps.cache-awslc.outputs.cache-hit }} + if: ${{ contains(matrix.build.install_steps, 'awslc') && !steps.cache1-awslc.outputs.cache-hit }} run: | curl --disable --fail --silent --show-error --connect-timeout 15 --max-time 120 --retry 6 --retry-connrefused \ --location --proto-redir =https "https://github.com/awslabs/aws-lc/archive/refs/tags/v${AWSLC_VERSION}.tar.gz" --output pkg.bin sha256sum pkg.bin && tar -xzf pkg.bin && rm -f pkg.bin cd "aws-lc-${AWSLC_VERSION}" - cmake -B . -G Ninja -DBUILD_TESTING=OFF -DBUILD_TOOL=OFF -DCMAKE_INSTALL_PREFIX=/home/runner/awslc + cmake -B . -G Ninja -DBUILD_TESTING=OFF -DBUILD_TOOL=OFF -DCMAKE_INSTALL_PREFIX=/home/runner/pkg/awslc cmake --build . cmake --install . - name: 'cache boringssl' if: ${{ contains(matrix.build.install_steps, 'boringssl') }} uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 - id: cache-boringssl + id: cache1-boringssl env: - cache-name: cache-boringssl + cache-name: cache1-boringssl with: - path: ~/boringssl - key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ env.BORINGSSL_VERSION }} + path: ~/pkg/boringssl + key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ env.BORINGSSL_VERSION }}-${{ runner.arch }} - name: 'build boringssl' - if: ${{ contains(matrix.build.install_steps, 'boringssl') && !steps.cache-boringssl.outputs.cache-hit }} + if: ${{ contains(matrix.build.install_steps, 'boringssl') && !steps.cache1-boringssl.outputs.cache-hit }} run: | curl --disable --fail --silent --show-error --connect-timeout 15 --max-time 120 --retry 6 --retry-connrefused \ --location --proto-redir =https "https://github.com/google/boringssl/releases/download/${BORINGSSL_VERSION}/boringssl-${BORINGSSL_VERSION}.tar.gz" --output pkg.bin sha256sum pkg.bin && tar -xzf pkg.bin && rm -f pkg.bin cd "boringssl-${BORINGSSL_VERSION}" - cmake -B . -G Ninja -DBUILD_TESTING=OFF -DCMAKE_INSTALL_PREFIX=/home/runner/boringssl + cmake -B . -G Ninja -DBUILD_TESTING=OFF -DCMAKE_INSTALL_PREFIX=/home/runner/pkg/boringssl cmake --build . cmake --install . - - name: 'cache rustls (intel)' - if: ${{ contains(matrix.build.install_steps, 'rustls-intel') }} + - name: 'cache rustls' + if: ${{ contains(matrix.build.install_steps, 'rustls') }} uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 - id: cache-rustls-intel + id: cache1-rustls env: - cache-name: cache-rustls-intel + cache-name: cache1-rustls with: path: ~/rustls - key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ env.RUSTLS_VERSION }} + key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ env.RUSTLS_VERSION }}-${{ runner.arch }} - - name: 'install rustls (intel)' - if: ${{ contains(matrix.build.install_steps, 'rustls-intel') && !steps.cache-rustls-intel.outputs.cache-hit }} - run: | - cd ~ - curl --disable --fail --silent --show-error --connect-timeout 15 --max-time 120 --retry 6 --retry-connrefused \ - --location --proto-redir =https "https://github.com/rustls/rustls-ffi/releases/download/v${RUSTLS_VERSION}/rustls-ffi-x86_64-linux-gnu.zip" --output pkg.bin - sha256sum pkg.bin && unzip pkg.bin -d rustls && rm -f pkg.bin - sed -i.bak 's|prefix=dist|prefix=/home/runner/rustls|g' rustls/lib/pkgconfig/rustls.pc; cat rustls/lib/pkgconfig/rustls.pc - - - name: 'cache rustls (arm)' - if: ${{ contains(matrix.build.install_steps, 'rustls-arm') }} - uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 - id: cache-rustls-arm - env: - cache-name: cache-rustls-arm - with: - path: ~/rustls - key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ env.RUSTLS_VERSION }} - - - name: 'install rustls (arm)' - if: ${{ contains(matrix.build.install_steps, 'rustls-arm') && !steps.cache-rustls-arm.outputs.cache-hit }} + - name: 'install rustls' + if: ${{ contains(matrix.build.install_steps, 'rustls') && !steps.cache1-rustls.outputs.cache-hit }} run: | cd ~ + if [ "$(uname -m)" = 'aarch64' ]; then + arch='arm64' + else + arch='x86_64' + fi curl --disable --fail --silent --show-error --connect-timeout 15 --max-time 120 --retry 6 --retry-connrefused \ - --location --proto-redir =https "https://github.com/rustls/rustls-ffi/releases/download/v${RUSTLS_VERSION}/rustls-ffi-arm64-linux-gnu.zip" --output pkg.bin + --location --proto-redir =https "https://github.com/rustls/rustls-ffi/releases/download/v${RUSTLS_VERSION}/rustls-ffi-${arch}-linux-gnu.zip" --output pkg.bin sha256sum pkg.bin && unzip pkg.bin -d rustls && rm -f pkg.bin sed -i.bak 's|prefix=dist|prefix=/home/runner/rustls|g' rustls/lib/pkgconfig/rustls.pc; cat rustls/lib/pkgconfig/rustls.pc From 6b506de183dafdb1e57072297d9730cfc8b81513 Mon Sep 17 00:00:00 2001 From: Viktor Szakats Date: Fri, 2 Oct 2026 15:25:36 +0200 Subject: [PATCH 366/481] GHA/linux: move the other Linux clang-tidy job to arm64 To improve performance and efficiency. Before: https://github.com/curl/curl/actions/runs/37007778644/job/110840014071 5m47s After: https://github.com/curl/curl/actions/runs/37013317312/job/110858047710 4m27s Follow-up to 9fd900ca54378cba4102dfb9fcb2d0a6428a50dc #23233 Follow-up to c431936d5b7452061412b10d7dce5f38fe489db3 #23232 Closes #23235 --- .github/workflows/linux.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 9bf4beb7b284..324563619350 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -337,7 +337,7 @@ jobs: configure: --without-ssl --enable-debug --disable-http --disable-smtp --disable-imap --disable-unity --enable-httpsig - name: 'clang-tidy' - image: ubuntu-26.04 + image: ubuntu-26.04-arm install_packages: clang-22 clang-tidy-22 libssl-dev libidn2-dev libssh2-1-dev libnghttp2-dev libldap-dev libkrb5-dev libgnutls28-dev install_steps: skiprun mbedtls-latest rustls wolfssl-opensslextra install_steps_brew: openssl@4 gsasl From 6c7237191cd31774ec8c15f441f00934066c9947 Mon Sep 17 00:00:00 2001 From: Viktor Szakats Date: Thu, 1 Oct 2026 16:24:46 +0200 Subject: [PATCH 367/481] GHA/http3-linux: add CPU and revision number to cache keys, switch 2 jobs to arm64 Also move all local build installs under the `~/pkg/` root, replacing `~//build/` and syncing with GHA/linux. Except quiche, which is a Rust project and lacks (or I could not find) a way to do a classic 'install' to a custom prefix. - include the CPU architecture in the cache key name. To allow cache blobs adapt automatically to the runner machine arch. It allows to transparently use the arm runner machines for any jobs. - add a revision number ('1') to the cache keys and IDs. To make it easy to bump when doing modifications like this, or just want to make test that need rebuilding them. - merge two stray quiche verbose build flags into `-vv`. (Useful to see BoringSSL build progress) - tried moving quiche from the default `target` subdir into the `~/pkg/` root. It failed because quiche headers remain inside the source tree, `~/quiche/quiche/include`, and missing from the cache. - build all cached dependencies for arm64 as well. - switch BoringSSL jobs to arm64. Note: - quiche has a valgrind issue, OpenSSL 3 and 4 another one, when built on arm64. Not tackled in this PR. Datapoint: Rebuilding all cached dependencies takes: - arm64: 12.5 minutes - x86_64: 17 minutes Ref: https://github.com/curl/curl/actions/runs/37016536935 In the actual jobs, arm64 is slightly faster in places, similar in most others: x64: https://github.com/curl/curl/actions/runs/37019925832 arm64: https://github.com/curl/curl/actions/runs/37026860992 Possible minor downside: - actual jobs now need to wait for both arm64 and intel cache builds to complete if any of the dependencies change. This happens rarely. It may also take a little bit longer to verify if the caches need to be rebuilt. Follow-up to 9fd900ca54378cba4102dfb9fcb2d0a6428a50dc #23233 Closes #23214 --- .github/workflows/http3-linux.yml | 475 +++++++++++++++--------------- 1 file changed, 239 insertions(+), 236 deletions(-) diff --git a/.github/workflows/http3-linux.yml b/.github/workflows/http3-linux.yml index 5c73c3361e9e..2c08f727201b 100644 --- a/.github/workflows/http3-linux.yml +++ b/.github/workflows/http3-linux.yml @@ -70,153 +70,156 @@ env: jobs: build-cache: - name: 'Build caches' - runs-on: ubuntu-26.04 + name: 'Build caches ${{ matrix.image }}' + runs-on: ${{ matrix.image }} + strategy: + matrix: + image: [ubuntu-26.04, ubuntu-26.04-arm] steps: - name: 'cache awslc' uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 - id: cache-awslc + id: cache1-awslc env: - cache-name: cache-awslc + cache-name: cache1-awslc with: - path: ~/awslc/build - key: ${{ runner.os }}-http3-build-${{ env.cache-name }}-${{ env.AWSLC_VERSION }} + path: ~/pkg/awslc + key: ${{ runner.os }}-http3-build-${{ env.cache-name }}-${{ env.AWSLC_VERSION }}-${{ runner.arch }} - name: 'cache boringssl' uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 - id: cache-boringssl + id: cache1-boringssl env: - cache-name: cache-boringssl + cache-name: cache1-boringssl with: - path: ~/boringssl/build - key: ${{ runner.os }}-http3-build-${{ env.cache-name }}-${{ env.BORINGSSL_VERSION }} + path: ~/pkg/boringssl + key: ${{ runner.os }}-http3-build-${{ env.cache-name }}-${{ env.BORINGSSL_VERSION }}-${{ runner.arch }} - name: 'cache nettle' uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 - id: cache-nettle + id: cache1-nettle env: - cache-name: cache-nettle + cache-name: cache1-nettle with: - path: ~/nettle/build - key: ${{ runner.os }}-http3-build-${{ env.cache-name }}-${{ env.NETTLE_VERSION }} + path: ~/pkg/nettle + key: ${{ runner.os }}-http3-build-${{ env.cache-name }}-${{ env.NETTLE_VERSION }}-${{ runner.arch }} - name: 'cache gnutls' uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 - id: cache-gnutls + id: cache1-gnutls env: - cache-name: cache-gnutls + cache-name: cache1-gnutls with: - path: ~/gnutls/build - key: ${{ runner.os }}-http3-build-${{ env.cache-name }}-${{ env.GNUTLS_VERSION }}-${{ env.NETTLE_VERSION }} + path: ~/pkg/gnutls + key: ${{ runner.os }}-http3-build-${{ env.cache-name }}-${{ env.GNUTLS_VERSION }}-${{ env.NETTLE_VERSION }}-${{ runner.arch }} - name: 'cache libressl' uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 - id: cache-libressl + id: cache1-libressl env: - cache-name: cache-libressl + cache-name: cache1-libressl with: - path: ~/libressl/build - key: ${{ runner.os }}-http3-build-${{ env.cache-name }}-${{ env.LIBRESSL_VERSION }} + path: ~/pkg/libressl + key: ${{ runner.os }}-http3-build-${{ env.cache-name }}-${{ env.LIBRESSL_VERSION }}-${{ runner.arch }} - name: 'cache openssl' uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 - id: cache-openssl-http3-no-deprecated + id: cache1-openssl-http3-no-deprecated env: - cache-name: cache-openssl-http3-no-deprecated + cache-name: cache1-openssl-http3-no-deprecated with: - path: ~/openssl/build - key: ${{ runner.os }}-http3-build-${{ env.cache-name }}-${{ env.OPENSSL_VERSION }} + path: ~/pkg/openssl + key: ${{ runner.os }}-http3-build-${{ env.cache-name }}-${{ env.OPENSSL_VERSION }}-${{ runner.arch }} - name: 'cache openssl-prev' uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 - id: cache-openssl-prev-http3 + id: cache1-openssl-prev-http3 env: - cache-name: cache-openssl-prev-http3 + cache-name: cache1-openssl-prev-http3 with: - path: ~/openssl-prev/build - key: ${{ runner.os }}-http3-build-${{ env.cache-name }}-${{ env.OPENSSL_PREV_VERSION }} + path: ~/pkg/openssl-prev + key: ${{ runner.os }}-http3-build-${{ env.cache-name }}-${{ env.OPENSSL_PREV_VERSION }}-${{ runner.arch }} - name: 'cache wolfssl' uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 - id: cache-wolfssl + id: cache1-wolfssl env: - cache-name: cache-wolfssl + cache-name: cache1-wolfssl with: - path: ~/wolfssl/build - key: ${{ runner.os }}-http3-build-${{ env.cache-name }}-${{ env.WOLFSSL_VERSION }} + path: ~/pkg/wolfssl + key: ${{ runner.os }}-http3-build-${{ env.cache-name }}-${{ env.WOLFSSL_VERSION }}-${{ runner.arch }} - name: 'cache nghttp3' uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 - id: cache-nghttp3 + id: cache1-nghttp3 env: - cache-name: cache-nghttp3 + cache-name: cache1-nghttp3 with: - path: ~/nghttp3/build - key: ${{ runner.os }}-http3-build-${{ env.cache-name }}-${{ env.NGHTTP3_VERSION }} + path: ~/pkg/nghttp3 + key: ${{ runner.os }}-http3-build-${{ env.cache-name }}-${{ env.NGHTTP3_VERSION }}-${{ runner.arch }} - name: 'cache ngtcp2' uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 - id: cache-ngtcp2 + id: cache1-ngtcp2 env: - cache-name: cache-ngtcp2 + cache-name: cache1-ngtcp2 with: - path: ~/ngtcp2/build + path: ~/pkg/ngtcp2 key: "${{ runner.os }}-http3-build-${{ env.cache-name }}-${{ env.NGTCP2_VERSION }}-${{ env.OPENSSL_VERSION }}-\ - ${{ env.LIBRESSL_VERSION }}-${{ env.AWSLC_VERSION }}-${{ env.NETTLE_VERSION }}-${{ env.GNUTLS_VERSION }}-${{ env.WOLFSSL_VERSION }}" + ${{ env.LIBRESSL_VERSION }}-${{ env.AWSLC_VERSION }}-${{ env.NETTLE_VERSION }}-${{ env.GNUTLS_VERSION }}-${{ env.WOLFSSL_VERSION }}-${{ runner.arch }}" - name: 'cache ngtcp2 boringssl' uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 - id: cache-ngtcp2-boringssl + id: cache1-ngtcp2-boringssl env: - cache-name: cache-ngtcp2-boringssl + cache-name: cache1-ngtcp2-boringssl with: - path: ~/ngtcp2-boringssl/build - key: ${{ runner.os }}-http3-build-${{ env.cache-name }}-${{ env.NGTCP2_VERSION }}-${{ env.BORINGSSL_VERSION }} + path: ~/pkg/ngtcp2-boringssl + key: ${{ runner.os }}-http3-build-${{ env.cache-name }}-${{ env.NGTCP2_VERSION }}-${{ env.BORINGSSL_VERSION }}-${{ runner.arch }} - name: 'cache ngtcp2 openssl-prev' uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 - id: cache-ngtcp2-openssl-prev + id: cache1-ngtcp2-openssl-prev env: - cache-name: cache-ngtcp2-openssl-prev + cache-name: cache1-ngtcp2-openssl-prev with: - path: ~/ngtcp2-openssl-prev/build - key: ${{ runner.os }}-http3-build-${{ env.cache-name }}-${{ env.NGTCP2_VERSION }}-${{ env.OPENSSL_PREV_VERSION }} + path: ~/pkg/ngtcp2-openssl-prev + key: ${{ runner.os }}-http3-build-${{ env.cache-name }}-${{ env.NGTCP2_VERSION }}-${{ env.OPENSSL_PREV_VERSION }}-${{ runner.arch }} - name: 'cache nghttp2' uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 - id: cache-nghttp2 + id: cache1-nghttp2 env: - cache-name: cache-nghttp2 + cache-name: cache1-nghttp2 with: - path: ~/nghttp2/build + path: ~/pkg/nghttp2 key: "${{ runner.os }}-http3-build-${{ env.cache-name }}-${{ env.NGHTTP2_VERSION }}-${{ env.OPENSSL_VERSION }}-\ - ${{ env.NGTCP2_VERSION }}-${{ env.NGHTTP3_VERSION }}" + ${{ env.NGTCP2_VERSION }}-${{ env.NGHTTP3_VERSION }}-${{ runner.arch }}" - name: 'cache h2o' uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 - id: cache-h2o + id: cache1-h2o env: - cache-name: cache-h2o + cache-name: cache1-h2o with: - path: ~/h2o/build - key: "${{ runner.os }}-http3-build-${{ env.cache-name }}-${{ env.H2O_VERSION }}-${{ env.OPENSSL_PREV_VERSION }}" + path: ~/pkg/h2o + key: "${{ runner.os }}-http3-build-${{ env.cache-name }}-${{ env.H2O_VERSION }}-${{ env.OPENSSL_PREV_VERSION }}-${{ runner.arch }}" - id: settings if: >- - ${{ !steps.cache-awslc.outputs.cache-hit || - !steps.cache-boringssl.outputs.cache-hit || - !steps.cache-nettle.outputs.cache-hit || - !steps.cache-gnutls.outputs.cache-hit || - !steps.cache-libressl.outputs.cache-hit || - !steps.cache-openssl-http3-no-deprecated.outputs.cache-hit || - !steps.cache-openssl-prev-http3.outputs.cache-hit || - !steps.cache-wolfssl.outputs.cache-hit || - !steps.cache-nghttp3.outputs.cache-hit || - !steps.cache-ngtcp2-boringssl.outputs.cache-hit || - !steps.cache-ngtcp2-openssl-prev.outputs.cache-hit || - !steps.cache-ngtcp2.outputs.cache-hit || - !steps.cache-nghttp2.outputs.cache-hit || - !steps.cache-h2o.outputs.cache-hit }} + ${{ !steps.cache1-awslc.outputs.cache-hit || + !steps.cache1-boringssl.outputs.cache-hit || + !steps.cache1-nettle.outputs.cache-hit || + !steps.cache1-gnutls.outputs.cache-hit || + !steps.cache1-libressl.outputs.cache-hit || + !steps.cache1-openssl-http3-no-deprecated.outputs.cache-hit || + !steps.cache1-openssl-prev-http3.outputs.cache-hit || + !steps.cache1-wolfssl.outputs.cache-hit || + !steps.cache1-nghttp3.outputs.cache-hit || + !steps.cache1-ngtcp2-boringssl.outputs.cache-hit || + !steps.cache1-ngtcp2-openssl-prev.outputs.cache-hit || + !steps.cache1-ngtcp2.outputs.cache-hit || + !steps.cache1-nghttp2.outputs.cache-hit || + !steps.cache1-h2o.outputs.cache-hit }} run: echo 'needs-build=true' >> "$GITHUB_OUTPUT" @@ -234,31 +237,31 @@ jobs: libp11-kit-dev autopoint bison gperf gtk-doc-tools libtasn1-bin - name: 'build awslc' - if: ${{ !steps.cache-awslc.outputs.cache-hit }} + if: ${{ !steps.cache1-awslc.outputs.cache-hit }} run: | cd ~ curl --disable --fail --silent --show-error --connect-timeout 15 --max-time 120 --retry 6 --retry-connrefused \ --location --proto-redir =https "https://github.com/awslabs/aws-lc/archive/refs/tags/v${AWSLC_VERSION}.tar.gz" --output pkg.bin sha256sum pkg.bin && tar -xzf pkg.bin && rm -f pkg.bin cd "aws-lc-${AWSLC_VERSION}" - cmake -B . -G Ninja -DBUILD_SHARED_LIBS=ON -DBUILD_TESTING=OFF -DBUILD_TOOL=OFF -DCMAKE_INSTALL_PREFIX=/home/runner/awslc/build + cmake -B . -G Ninja -DBUILD_SHARED_LIBS=ON -DBUILD_TESTING=OFF -DBUILD_TOOL=OFF -DCMAKE_INSTALL_PREFIX=/home/runner/pkg/awslc cmake --build . cmake --install . - name: 'build boringssl' - if: ${{ !steps.cache-boringssl.outputs.cache-hit }} + if: ${{ !steps.cache1-boringssl.outputs.cache-hit }} run: | cd ~ curl --disable --fail --silent --show-error --connect-timeout 15 --max-time 120 --retry 6 --retry-connrefused \ --location --proto-redir =https "https://github.com/google/boringssl/releases/download/${BORINGSSL_VERSION}/boringssl-${BORINGSSL_VERSION}.tar.gz" --output pkg.bin sha256sum pkg.bin && tar -xzf pkg.bin && rm -f pkg.bin cd "boringssl-${BORINGSSL_VERSION}" - cmake -B . -G Ninja -DBUILD_SHARED_LIBS=ON -DBUILD_TESTING=OFF -DCMAKE_INSTALL_PREFIX=/home/runner/boringssl/build + cmake -B . -G Ninja -DBUILD_SHARED_LIBS=ON -DBUILD_TESTING=OFF -DCMAKE_INSTALL_PREFIX=/home/runner/pkg/boringssl cmake --build . cmake --install . - name: 'build nettle' - if: ${{ !steps.cache-nettle.outputs.cache-hit }} + if: ${{ !steps.cache1-nettle.outputs.cache-hit }} run: | cd ~ curl --disable --fail --silent --show-error --connect-timeout 15 --max-time 120 --retry 6 --retry-connrefused \ @@ -266,12 +269,12 @@ jobs: sha256sum pkg.bin && tar -xzf pkg.bin && rm -f pkg.bin cd "nettle-${NETTLE_VERSION}" autoreconf -fi - ./configure --disable-dependency-tracking --prefix=/home/runner/nettle/build \ + ./configure --disable-dependency-tracking --prefix=/home/runner/pkg/nettle \ --disable-static --disable-openssl --disable-documentation make install - name: 'build gnutls' - if: ${{ !steps.cache-gnutls.outputs.cache-hit }} + if: ${{ !steps.cache1-gnutls.outputs.cache-hit }} run: | cd ~ curl --disable --fail --silent --show-error --connect-timeout 15 --max-time 120 --retry 6 --retry-connrefused \ @@ -280,121 +283,121 @@ jobs: cd "gnutls-${GNUTLS_VERSION}" autoreconf -fi # required: libp11-kit-dev libev-dev autopoint bison gperf gtk-doc-tools libtasn1-bin - ./configure --disable-dependency-tracking --prefix=/home/runner/gnutls/build \ - PKG_CONFIG_PATH=/home/runner/nettle/build/lib/pkgconfig \ - LDFLAGS=-Wl,-rpath,/home/runner/nettle/build/lib \ + ./configure --disable-dependency-tracking --prefix=/home/runner/pkg/gnutls \ + PKG_CONFIG_PATH=/home/runner/pkg/nettle/lib/pkgconfig \ + LDFLAGS=-Wl,-rpath,/home/runner/pkg/nettle/lib \ --with-included-libtasn1 --with-included-unistring \ --disable-doc --disable-tests --disable-tools make install - name: 'build libressl' - if: ${{ !steps.cache-libressl.outputs.cache-hit }} + if: ${{ !steps.cache1-libressl.outputs.cache-hit }} run: | cd ~ curl --disable --fail --silent --show-error --connect-timeout 15 --max-time 120 --retry 6 --retry-connrefused \ "https://ftp.openbsd.org/pub/OpenBSD/LibreSSL/libressl-${LIBRESSL_VERSION}.tar.gz" --output pkg.bin sha256sum pkg.bin && tar -xzf pkg.bin && rm -f pkg.bin cd "libressl-${LIBRESSL_VERSION}" - cmake -B . -G Ninja -DLIBRESSL_APPS=OFF -DLIBRESSL_TESTS=OFF -DCMAKE_INSTALL_PREFIX=/home/runner/libressl/build + cmake -B . -G Ninja -DLIBRESSL_APPS=OFF -DLIBRESSL_TESTS=OFF -DCMAKE_INSTALL_PREFIX=/home/runner/pkg/libressl cmake --build . cmake --install . - name: 'build openssl' - if: ${{ !steps.cache-openssl-http3-no-deprecated.outputs.cache-hit }} + if: ${{ !steps.cache1-openssl-http3-no-deprecated.outputs.cache-hit }} run: | cd ~ git clone --quiet --depth 1 --branch "openssl-${OPENSSL_VERSION}" https://github.com/openssl/openssl cd openssl - ./config --prefix="$PWD"/build --libdir=lib no-makedepend no-apps no-docs no-tests no-deprecated + ./config --prefix=/home/runner/pkg/openssl --libdir=lib no-makedepend no-apps no-docs no-tests no-deprecated make make -j1 install_sw - name: 'build openssl-prev' - if: ${{ !steps.cache-openssl-prev-http3.outputs.cache-hit }} + if: ${{ !steps.cache1-openssl-prev-http3.outputs.cache-hit }} run: | cd ~ curl --disable --fail --silent --show-error --connect-timeout 15 --max-time 120 --retry 6 --retry-connrefused \ --location --proto-redir =https "https://github.com/openssl/openssl/releases/download/openssl-${OPENSSL_PREV_VERSION}/openssl-${OPENSSL_PREV_VERSION}.tar.gz" --output pkg.bin sha256sum pkg.bin | tee /dev/stderr | grep -qwF -- "${OPENSSL_PREV_SHA256}" && tar -xzf pkg.bin && rm -f pkg.bin cd "openssl-${OPENSSL_PREV_VERSION}" - ./config --prefix=/home/runner/openssl-prev/build --libdir=lib no-makedepend no-apps no-docs no-tests + ./config --prefix=/home/runner/pkg/openssl-prev --libdir=lib no-makedepend no-apps no-docs no-tests make make -j1 install_sw - name: 'build wolfssl' - if: ${{ !steps.cache-wolfssl.outputs.cache-hit }} + if: ${{ !steps.cache1-wolfssl.outputs.cache-hit }} run: | cd ~ git clone --quiet --depth 1 --branch "v${WOLFSSL_VERSION}-stable" https://github.com/wolfSSL/wolfssl cd wolfssl ./autogen.sh - ./configure --disable-dependency-tracking --prefix="$PWD"/build --enable-all --enable-quic \ + ./configure --disable-dependency-tracking --prefix=/home/runner/pkg/wolfssl --enable-all --enable-quic \ --disable-benchmark --disable-crypttests --disable-examples make make install - name: 'build nghttp3' - if: ${{ !steps.cache-nghttp3.outputs.cache-hit }} + if: ${{ !steps.cache1-nghttp3.outputs.cache-hit }} run: | cd ~ git clone --quiet --depth 1 --branch "v${NGHTTP3_VERSION}" https://github.com/ngtcp2/nghttp3 cd nghttp3 git submodule update --init --depth 1 autoreconf -fi - ./configure --disable-dependency-tracking --prefix="$PWD"/build --enable-lib-only + ./configure --disable-dependency-tracking --prefix=/home/runner/pkg/nghttp3 --enable-lib-only make make install - name: 'build ngtcp2' - if: ${{ !steps.cache-ngtcp2.outputs.cache-hit }} + if: ${{ !steps.cache1-ngtcp2.outputs.cache-hit }} # building twice to get crypto libs for ossl, libressl and awslc installed run: | cd ~ git clone --quiet --depth 1 --branch "v${NGTCP2_VERSION}" https://github.com/ngtcp2/ngtcp2 cd ngtcp2 autoreconf -fi - ./configure --disable-dependency-tracking --prefix="$PWD"/build --enable-lib-only \ - PKG_CONFIG_PATH=/home/runner/libressl/build/lib/pkgconfig \ + ./configure --disable-dependency-tracking --prefix=/home/runner/pkg/ngtcp2 --enable-lib-only \ + PKG_CONFIG_PATH=/home/runner/pkg/libressl/lib/pkgconfig \ --with-openssl make install make clean - export PKG_CONFIG_PATH=/home/runner/openssl/build/lib/pkgconfig - PKG_CONFIG_PATH+=:/home/runner/nettle/build/lib/pkgconfig - PKG_CONFIG_PATH+=:/home/runner/gnutls/build/lib/pkgconfig - PKG_CONFIG_PATH+=:/home/runner/wolfssl/build/lib/pkgconfig - ./configure --disable-dependency-tracking --prefix="$PWD"/build --enable-lib-only \ + export PKG_CONFIG_PATH=/home/runner/pkg/openssl/lib/pkgconfig + PKG_CONFIG_PATH+=:/home/runner/pkg/nettle/lib/pkgconfig + PKG_CONFIG_PATH+=:/home/runner/pkg/gnutls/lib/pkgconfig + PKG_CONFIG_PATH+=:/home/runner/pkg/wolfssl/lib/pkgconfig + ./configure --disable-dependency-tracking --prefix=/home/runner/pkg/ngtcp2 --enable-lib-only \ --with-openssl --with-gnutls --with-wolfssl --with-boringssl \ - BORINGSSL_LIBS='-L/home/runner/awslc/build/lib -lssl -lcrypto' \ - BORINGSSL_CFLAGS='-I/home/runner/awslc/build/include' + BORINGSSL_LIBS='-L/home/runner/pkg/awslc/lib -lssl -lcrypto' \ + BORINGSSL_CFLAGS='-I/home/runner/pkg/awslc/include' make install - name: 'build ngtcp2 openssl-prev' - if: ${{ !steps.cache-ngtcp2-openssl-prev.outputs.cache-hit }} + if: ${{ !steps.cache1-ngtcp2-openssl-prev.outputs.cache-hit }} run: | cd ~ git clone --quiet --depth 1 --branch "v${NGTCP2_VERSION}" https://github.com/ngtcp2/ngtcp2 ngtcp2-openssl-prev cd ngtcp2-openssl-prev autoreconf -fi - ./configure --disable-dependency-tracking --prefix="$PWD"/build --enable-lib-only \ - PKG_CONFIG_PATH=/home/runner/openssl-prev/build/lib/pkgconfig \ + ./configure --disable-dependency-tracking --prefix=/home/runner/pkg/ngtcp2-openssl-prev --enable-lib-only \ + PKG_CONFIG_PATH=/home/runner/pkg/openssl-prev/lib/pkgconfig \ --with-openssl make install - name: 'build ngtcp2 boringssl' - if: ${{ !steps.cache-ngtcp2-boringssl.outputs.cache-hit }} + if: ${{ !steps.cache1-ngtcp2-boringssl.outputs.cache-hit }} run: | cd ~ git clone --quiet --depth 1 --branch "v${NGTCP2_VERSION}" https://github.com/ngtcp2/ngtcp2 ngtcp2-boringssl cd ngtcp2-boringssl autoreconf -fi - ./configure --disable-dependency-tracking --prefix="$PWD"/build --enable-lib-only \ + ./configure --disable-dependency-tracking --prefix=/home/runner/pkg/ngtcp2-boringssl --enable-lib-only \ --with-openssl=no --with-boringssl \ - BORINGSSL_LIBS='-L/home/runner/boringssl/build/lib -lssl -lcrypto' \ - BORINGSSL_CFLAGS='-I/home/runner/boringssl/build/include' + BORINGSSL_LIBS='-L/home/runner/pkg/boringssl/lib -lssl -lcrypto' \ + BORINGSSL_CFLAGS='-I/home/runner/pkg/boringssl/include' make install - name: 'build nghttp2' - if: ${{ !steps.cache-nghttp2.outputs.cache-hit }} + if: ${{ !steps.cache1-nghttp2.outputs.cache-hit }} run: | cd ~ git clone --quiet --depth 1 --branch "v${NGHTTP2_VERSION}" https://github.com/nghttp2/nghttp2 @@ -403,30 +406,30 @@ jobs: autoreconf -fi # required (for nghttpx application): libc-ares-dev libev-dev zlib1g-dev # optional (for nghttpx application): libbrotli-dev - export PKG_CONFIG_PATH=/home/runner/openssl/build/lib/pkgconfig - PKG_CONFIG_PATH+=:/home/runner/nghttp3/build/lib/pkgconfig - PKG_CONFIG_PATH+=:/home/runner/ngtcp2/build/lib/pkgconfig - ./configure --disable-dependency-tracking --prefix="$PWD"/build --enable-app --enable-http3 \ - LDFLAGS=-Wl,-rpath,/home/runner/openssl/build/lib \ + export PKG_CONFIG_PATH=/home/runner/pkg/openssl/lib/pkgconfig + PKG_CONFIG_PATH+=:/home/runner/pkg/nghttp3/lib/pkgconfig + PKG_CONFIG_PATH+=:/home/runner/pkg/ngtcp2/lib/pkgconfig + ./configure --disable-dependency-tracking --prefix=/home/runner/pkg/nghttp2 --enable-app --enable-http3 \ + LDFLAGS=-Wl,-rpath,/home/runner/pkg/openssl/lib \ --with-libbrotlienc --with-libbrotlidec make install - name: 'build h2o' - if: ${{ !steps.cache-h2o.outputs.cache-hit }} + if: ${{ !steps.cache1-h2o.outputs.cache-hit }} run: | cd ~ curl --disable --fail --silent --show-error --connect-timeout 15 --max-time 120 --retry 6 --retry-connrefused \ --location --proto-redir =https "https://github.com/h2o/h2o/archive/${H2O_VERSION}.tar.gz" --output pkg.bin sha256sum pkg.bin | tee /dev/stderr | grep -qwF -- "${H2O_SHA256}" && tar -xzf pkg.bin && rm -f pkg.bin cd "h2o-${H2O_VERSION}" - cmake -B . -G Ninja -DWITHOUT_LIBS=ON -DOPENSSL_ROOT_DIR=/home/runner/openssl-prev/build -DCMAKE_INSTALL_PREFIX=/home/runner/h2o/build + cmake -B . -G Ninja -DWITHOUT_LIBS=ON -DOPENSSL_ROOT_DIR=/home/runner/pkg/openssl-prev -DCMAKE_INSTALL_PREFIX=/home/runner/pkg/h2o cmake --build . cmake --install . linux: name: ${{ matrix.build.generate && 'CM' || 'AM' }} ${{ matrix.build.name }} needs: build-cache - runs-on: ubuntu-26.04 + runs-on: ${{ matrix.build.image || 'ubuntu-26.04' }} timeout-minutes: 10 env: CURL_TRACE_PKG_CONFIG: '1' @@ -437,106 +440,108 @@ jobs: build: - name: 'awslc' install_steps: skipall - LDFLAGS: -Wl,-rpath,/home/runner/awslc/build/lib - PKG_CONFIG_PATH: /home/runner/awslc/build/lib/pkgconfig:/home/runner/nghttp3/build/lib/pkgconfig:/home/runner/ngtcp2/build/lib/pkgconfig:/home/runner/nghttp2/build/lib/pkgconfig + LDFLAGS: -Wl,-rpath,/home/runner/pkg/awslc/lib + PKG_CONFIG_PATH: /home/runner/pkg/awslc/lib/pkgconfig:/home/runner/pkg/nghttp3/lib/pkgconfig:/home/runner/pkg/ngtcp2/lib/pkgconfig:/home/runner/pkg/nghttp2/lib/pkgconfig # Intentionally using bare '--with-ngtcp2' + 'PKG_CONFIG_PATH' to test this way of configuration, in addition to '--with-ngtcp2=' in other jobs. configure: >- - --with-openssl=/home/runner/awslc/build --with-ngtcp2 --enable-ssls-export + --with-openssl=/home/runner/pkg/awslc --with-ngtcp2 --enable-ssls-export - name: 'awslc' - PKG_CONFIG_PATH: /home/runner/awslc/build/lib/pkgconfig:/home/runner/nghttp3/build/lib/pkgconfig:/home/runner/ngtcp2/build/lib/pkgconfig:/home/runner/nghttp2/build/lib/pkgconfig + PKG_CONFIG_PATH: /home/runner/pkg/awslc/lib/pkgconfig:/home/runner/pkg/nghttp3/lib/pkgconfig:/home/runner/pkg/ngtcp2/lib/pkgconfig:/home/runner/pkg/nghttp2/lib/pkgconfig generate: >- - -DOPENSSL_ROOT_DIR=/home/runner/awslc/build -DUSE_NGTCP2=ON -DBUILD_SHARED_LIBS=OFF + -DOPENSSL_ROOT_DIR=/home/runner/pkg/awslc -DUSE_NGTCP2=ON -DBUILD_SHARED_LIBS=OFF -DCMAKE_UNITY_BUILD=ON -DCURL_DROP_UNUSED=ON -DCURL_DISABLE_HTTPSIG=OFF - name: 'boringssl' + image: ubuntu-26.04-arm install_steps: skipall - LDFLAGS: -Wl,-rpath,/home/runner/boringssl/build/lib - PKG_CONFIG_PATH: /home/runner/boringssl/build/lib/pkgconfig:/home/runner/nghttp3/build/lib/pkgconfig:/home/runner/nghttp2/build/lib/pkgconfig + LDFLAGS: -Wl,-rpath,/home/runner/pkg/boringssl/lib + PKG_CONFIG_PATH: /home/runner/pkg/boringssl/lib/pkgconfig:/home/runner/pkg/nghttp3/lib/pkgconfig:/home/runner/pkg/nghttp2/lib/pkgconfig configure: >- - --with-openssl=/home/runner/boringssl/build --with-ngtcp2=/home/runner/ngtcp2-boringssl/build --enable-ssls-export + --with-openssl=/home/runner/pkg/boringssl --with-ngtcp2=/home/runner/pkg/ngtcp2-boringssl --enable-ssls-export - name: 'boringssl' + image: ubuntu-26.04-arm PKG_CONFIG_PATH: "\ - /home/runner/boringssl/build/lib/pkgconfig:\ - /home/runner/nghttp3/build/lib/pkgconfig:\ - /home/runner/ngtcp2-boringssl/build/lib/pkgconfig:\ - /home/runner/nghttp2/build/lib/pkgconfig" + /home/runner/pkg/boringssl/lib/pkgconfig:\ + /home/runner/pkg/nghttp3/lib/pkgconfig:\ + /home/runner/pkg/ngtcp2-boringssl/lib/pkgconfig:\ + /home/runner/pkg/nghttp2/lib/pkgconfig" generate: >- - -DOPENSSL_ROOT_DIR=/home/runner/boringssl/build -DUSE_NGTCP2=ON -DBUILD_SHARED_LIBS=OFF + -DOPENSSL_ROOT_DIR=/home/runner/pkg/boringssl -DUSE_NGTCP2=ON -DBUILD_SHARED_LIBS=OFF -DCMAKE_UNITY_BUILD=ON -DCURL_DISABLE_HTTPSIG=OFF - name: 'gnutls' install_packages: libp11-kit-dev libssh-dev install_steps: skipall - LDFLAGS: -Wl,-rpath,/home/runner/gnutls/build/lib -Wl,-rpath,/home/runner/nettle/build/lib -Wl,-rpath,/home/runner/ngtcp2/build/lib - PKG_CONFIG_PATH: /home/runner/nettle/build/lib/pkgconfig:/home/runner/gnutls/build/lib/pkgconfig:/home/runner/nghttp3/build/lib/pkgconfig:/home/runner/nghttp2/build/lib/pkgconfig + LDFLAGS: -Wl,-rpath,/home/runner/pkg/gnutls/lib -Wl,-rpath,/home/runner/pkg/nettle/lib -Wl,-rpath,/home/runner/pkg/ngtcp2/lib + PKG_CONFIG_PATH: /home/runner/pkg/nettle/lib/pkgconfig:/home/runner/pkg/gnutls/lib/pkgconfig:/home/runner/pkg/nghttp3/lib/pkgconfig:/home/runner/pkg/nghttp2/lib/pkgconfig configure: >- - --with-gnutls=/home/runner/gnutls/build --with-ngtcp2=/home/runner/ngtcp2/build --with-libssh --enable-ssls-export --enable-httpsig --disable-ntlm + --with-gnutls=/home/runner/pkg/gnutls --with-ngtcp2=/home/runner/pkg/ngtcp2 --with-libssh --enable-ssls-export --enable-httpsig --disable-ntlm - name: 'gnutls' tflags: '--min=1900' install_packages: libp11-kit-dev libssh-dev - LDFLAGS: -Wl,-rpath,/home/runner/gnutls/build/lib + LDFLAGS: -Wl,-rpath,/home/runner/pkg/gnutls/lib PKG_CONFIG_PATH: "\ - /home/runner/nettle/build/lib/pkgconfig:\ - /home/runner/gnutls/build/lib/pkgconfig:\ - /home/runner/nghttp3/build/lib/pkgconfig:\ - /home/runner/ngtcp2/build/lib/pkgconfig:\ - /home/runner/nghttp2/build/lib/pkgconfig" + /home/runner/pkg/nettle/lib/pkgconfig:\ + /home/runner/pkg/gnutls/lib/pkgconfig:\ + /home/runner/pkg/nghttp3/lib/pkgconfig:\ + /home/runner/pkg/ngtcp2/lib/pkgconfig:\ + /home/runner/pkg/nghttp2/lib/pkgconfig" generate: >- -DCURL_USE_GNUTLS=ON -DUSE_NGTCP2=ON -DCURL_USE_LIBSSH=ON -DCMAKE_UNITY_BUILD=ON -DCURL_DISABLE_HTTPSIG=OFF -DCURL_ENABLE_NTLM=OFF - name: 'libressl' install_steps: skipall - LDFLAGS: -Wl,-rpath,/home/runner/libressl/build/lib - PKG_CONFIG_PATH: /home/runner/libressl/build/lib/pkgconfig:/home/runner/nghttp3/build/lib/pkgconfig:/home/runner/nghttp2/build/lib/pkgconfig + LDFLAGS: -Wl,-rpath,/home/runner/pkg/libressl/lib + PKG_CONFIG_PATH: /home/runner/pkg/libressl/lib/pkgconfig:/home/runner/pkg/nghttp3/lib/pkgconfig:/home/runner/pkg/nghttp2/lib/pkgconfig # Intentionally using '--with-ngtcp2=' to test this way of configuration, in addition to bare '--with-ngtcp2' + 'PKG_CONFIG_PATH' in other jobs. configure: >- - --with-openssl=/home/runner/libressl/build --with-ngtcp2=/home/runner/ngtcp2/build --enable-ssls-export + --with-openssl=/home/runner/pkg/libressl --with-ngtcp2=/home/runner/pkg/ngtcp2 --enable-ssls-export --enable-unity - name: 'libressl' - PKG_CONFIG_PATH: /home/runner/libressl/build/lib/pkgconfig:/home/runner/nghttp3/build/lib/pkgconfig:/home/runner/ngtcp2/build/lib/pkgconfig:/home/runner/nghttp2/build/lib/pkgconfig + PKG_CONFIG_PATH: /home/runner/pkg/libressl/lib/pkgconfig:/home/runner/pkg/nghttp3/lib/pkgconfig:/home/runner/pkg/ngtcp2/lib/pkgconfig:/home/runner/pkg/nghttp2/lib/pkgconfig generate: >- - -DOPENSSL_ROOT_DIR=/home/runner/libressl/build -DUSE_NGTCP2=ON -DCURL_DISABLE_HTTPSIG=OFF + -DOPENSSL_ROOT_DIR=/home/runner/pkg/libressl -DUSE_NGTCP2=ON -DCURL_DISABLE_HTTPSIG=OFF - name: 'openssl' tflags: '--min=1850' - LDFLAGS: -Wl,-rpath,/home/runner/openssl/build/lib - PKG_CONFIG_PATH: /home/runner/openssl/build/lib/pkgconfig:/home/runner/nghttp3/build/lib/pkgconfig:/home/runner/nghttp2/build/lib/pkgconfig + LDFLAGS: -Wl,-rpath,/home/runner/pkg/openssl/lib + PKG_CONFIG_PATH: /home/runner/pkg/openssl/lib/pkgconfig:/home/runner/pkg/nghttp3/lib/pkgconfig:/home/runner/pkg/nghttp2/lib/pkgconfig configure: >- - --with-openssl=/home/runner/openssl/build --with-ngtcp2=/home/runner/ngtcp2/build --enable-ech --enable-ssls-export --enable-proxy-http3 --enable-httpsig + --with-openssl=/home/runner/pkg/openssl --with-ngtcp2=/home/runner/pkg/ngtcp2 --enable-ech --enable-ssls-export --enable-proxy-http3 --enable-httpsig - name: 'openssl' install_steps: skipall - PKG_CONFIG_PATH: /home/runner/openssl/build/lib/pkgconfig:/home/runner/nghttp3/build/lib/pkgconfig:/home/runner/ngtcp2/build/lib/pkgconfig:/home/runner/nghttp2/build/lib/pkgconfig + PKG_CONFIG_PATH: /home/runner/pkg/openssl/lib/pkgconfig:/home/runner/pkg/nghttp3/lib/pkgconfig:/home/runner/pkg/ngtcp2/lib/pkgconfig:/home/runner/pkg/nghttp2/lib/pkgconfig generate: >- - -DOPENSSL_ROOT_DIR=/home/runner/openssl/build -DUSE_NGTCP2=ON + -DOPENSSL_ROOT_DIR=/home/runner/pkg/openssl -DUSE_NGTCP2=ON -DCURL_DISABLE_LDAP=ON -DUSE_ECH=ON -DUSE_PROXY_HTTP3=ON -DCMAKE_UNITY_BUILD=ON - name: 'openssl-prev' install_steps: skipall - LDFLAGS: -Wl,-rpath,/home/runner/openssl-prev/build/lib + LDFLAGS: -Wl,-rpath,/home/runner/pkg/openssl-prev/lib PKG_CONFIG_PATH: "\ - /home/runner/openssl-prev/build/lib/pkgconfig:\ - /home/runner/nghttp3/build/lib/pkgconfig:\ - /home/runner/nghttp2/build/lib/pkgconfig" + /home/runner/pkg/openssl-prev/lib/pkgconfig:\ + /home/runner/pkg/nghttp3/lib/pkgconfig:\ + /home/runner/pkg/nghttp2/lib/pkgconfig" configure: >- - --with-openssl=/home/runner/openssl-prev/build --with-ngtcp2=/home/runner/ngtcp2-openssl-prev/build --enable-ssls-export + --with-openssl=/home/runner/pkg/openssl-prev --with-ngtcp2=/home/runner/pkg/ngtcp2-openssl-prev --enable-ssls-export - name: 'openssl-prev' tflags: '--min=2000' PKG_CONFIG_PATH: "\ - /home/runner/openssl-prev/build/lib/pkgconfig:\ - /home/runner/nghttp3/build/lib/pkgconfig:\ - /home/runner/nghttp2/build/lib/pkgconfig:\ - /home/runner/ngtcp2-openssl-prev/build/lib/pkgconfig" + /home/runner/pkg/openssl-prev/lib/pkgconfig:\ + /home/runner/pkg/nghttp3/lib/pkgconfig:\ + /home/runner/pkg/nghttp2/lib/pkgconfig:\ + /home/runner/pkg/ngtcp2-openssl-prev/lib/pkgconfig" generate: >- - -DOPENSSL_ROOT_DIR=/home/runner/openssl-prev/build -DUSE_NGTCP2=ON + -DOPENSSL_ROOT_DIR=/home/runner/pkg/openssl-prev -DUSE_NGTCP2=ON -DCURL_DISABLE_LDAP=ON -DCURL_DISABLE_HTTPSIG=OFF - name: 'quiche' @@ -545,7 +550,7 @@ jobs: CC: clang CFLAGS: -fPIC LDFLAGS: -Wl,-rpath,/home/runner/quiche/target/release -lstdc++ - PKG_CONFIG_PATH: /home/runner/nghttp2/build/lib/pkgconfig + PKG_CONFIG_PATH: /home/runner/pkg/nghttp2/lib/pkgconfig configure: >- --with-openssl=/home/runner/quiche/boringssl --with-quiche=/home/runner/quiche/target/release @@ -555,7 +560,7 @@ jobs: - name: 'quiche' install_packages: clang CC: clang - PKG_CONFIG_PATH: /home/runner/nghttp2/build/lib/pkgconfig:/home/runner/quiche/target/release + PKG_CONFIG_PATH: /home/runner/pkg/nghttp2/lib/pkgconfig:/home/runner/quiche/target/release generate: >- -DOPENSSL_ROOT_DIR=/home/runner/quiche/boringssl -DOPENSSL_USE_STATIC_LIBS=ON -DUSE_QUICHE=ON @@ -564,16 +569,16 @@ jobs: - name: 'wolfssl' install_packages: libssh2-1-dev install_steps: skipall - LDFLAGS: -Wl,-rpath,/home/runner/wolfssl/build/lib - PKG_CONFIG_PATH: /home/runner/wolfssl/build/lib/pkgconfig:/home/runner/nghttp3/build/lib/pkgconfig:/home/runner/nghttp2/build/lib/pkgconfig + LDFLAGS: -Wl,-rpath,/home/runner/pkg/wolfssl/lib + PKG_CONFIG_PATH: /home/runner/pkg/wolfssl/lib/pkgconfig:/home/runner/pkg/nghttp3/lib/pkgconfig:/home/runner/pkg/nghttp2/lib/pkgconfig configure: >- - --with-wolfssl=/home/runner/wolfssl/build --with-ngtcp2=/home/runner/ngtcp2/build --enable-ech --with-libssh2 --enable-ssls-export + --with-wolfssl=/home/runner/pkg/wolfssl --with-ngtcp2=/home/runner/pkg/ngtcp2 --enable-ech --with-libssh2 --enable-ssls-export --enable-unity - name: 'wolfssl' install_packages: libssh2-1-dev tflags: '--min=1900' - PKG_CONFIG_PATH: /home/runner/wolfssl/build/lib/pkgconfig:/home/runner/nghttp3/build/lib/pkgconfig:/home/runner/ngtcp2/build/lib/pkgconfig:/home/runner/nghttp2/build/lib/pkgconfig + PKG_CONFIG_PATH: /home/runner/pkg/wolfssl/lib/pkgconfig:/home/runner/pkg/nghttp3/lib/pkgconfig:/home/runner/pkg/ngtcp2/lib/pkgconfig:/home/runner/pkg/nghttp2/lib/pkgconfig generate: >- -DCURL_USE_WOLFSSL=ON -DUSE_NGTCP2=ON -DUSE_ECH=ON -DCURL_DISABLE_HTTPSIG=OFF @@ -594,170 +599,170 @@ jobs: - name: 'cache awslc' if: ${{ contains(matrix.build.name, 'awslc') }} uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 - id: cache-awslc + id: cache1-awslc env: - cache-name: cache-awslc + cache-name: cache1-awslc with: - path: ~/awslc/build - key: ${{ runner.os }}-http3-build-${{ env.cache-name }}-${{ env.AWSLC_VERSION }} + path: ~/pkg/awslc + key: ${{ runner.os }}-http3-build-${{ env.cache-name }}-${{ env.AWSLC_VERSION }}-${{ runner.arch }} fail-on-cache-miss: true - name: 'cache boringssl' if: ${{ contains(matrix.build.name, 'boringssl') }} uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 - id: cache-boringssl + id: cache1-boringssl env: - cache-name: cache-boringssl + cache-name: cache1-boringssl with: - path: ~/boringssl/build - key: ${{ runner.os }}-http3-build-${{ env.cache-name }}-${{ env.BORINGSSL_VERSION }} + path: ~/pkg/boringssl + key: ${{ runner.os }}-http3-build-${{ env.cache-name }}-${{ env.BORINGSSL_VERSION }}-${{ runner.arch }} fail-on-cache-miss: true - name: 'cache nettle' if: ${{ contains(matrix.build.name, 'gnutls') }} uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 - id: cache-nettle + id: cache1-nettle env: - cache-name: cache-nettle + cache-name: cache1-nettle with: - path: ~/nettle/build - key: ${{ runner.os }}-http3-build-${{ env.cache-name }}-${{ env.NETTLE_VERSION }} + path: ~/pkg/nettle + key: ${{ runner.os }}-http3-build-${{ env.cache-name }}-${{ env.NETTLE_VERSION }}-${{ runner.arch }} fail-on-cache-miss: true - name: 'cache gnutls' if: ${{ contains(matrix.build.name, 'gnutls') }} uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 - id: cache-gnutls + id: cache1-gnutls env: - cache-name: cache-gnutls + cache-name: cache1-gnutls with: - path: ~/gnutls/build - key: ${{ runner.os }}-http3-build-${{ env.cache-name }}-${{ env.GNUTLS_VERSION }}-${{ env.NETTLE_VERSION }} + path: ~/pkg/gnutls + key: ${{ runner.os }}-http3-build-${{ env.cache-name }}-${{ env.GNUTLS_VERSION }}-${{ env.NETTLE_VERSION }}-${{ runner.arch }} fail-on-cache-miss: true - name: 'cache libressl' if: ${{ contains(matrix.build.name, 'libressl') }} uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 - id: cache-libressl + id: cache1-libressl env: - cache-name: cache-libressl + cache-name: cache1-libressl with: - path: ~/libressl/build - key: ${{ runner.os }}-http3-build-${{ env.cache-name }}-${{ env.LIBRESSL_VERSION }} + path: ~/pkg/libressl + key: ${{ runner.os }}-http3-build-${{ env.cache-name }}-${{ env.LIBRESSL_VERSION }}-${{ runner.arch }} fail-on-cache-miss: true - name: 'cache openssl' uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 - id: cache-openssl-http3-no-deprecated + id: cache1-openssl-http3-no-deprecated env: - cache-name: cache-openssl-http3-no-deprecated + cache-name: cache1-openssl-http3-no-deprecated with: - path: ~/openssl/build - key: ${{ runner.os }}-http3-build-${{ env.cache-name }}-${{ env.OPENSSL_VERSION }} + path: ~/pkg/openssl + key: ${{ runner.os }}-http3-build-${{ env.cache-name }}-${{ env.OPENSSL_VERSION }}-${{ runner.arch }} fail-on-cache-miss: true - name: 'cache openssl-prev' if: ${{ contains(matrix.build.name, 'openssl-prev') }} uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 - id: cache-openssl-prev-http3 + id: cache1-openssl-prev-http3 env: - cache-name: cache-openssl-prev-http3 + cache-name: cache1-openssl-prev-http3 with: - path: ~/openssl-prev/build - key: ${{ runner.os }}-http3-build-${{ env.cache-name }}-${{ env.OPENSSL_PREV_VERSION }} + path: ~/pkg/openssl-prev + key: ${{ runner.os }}-http3-build-${{ env.cache-name }}-${{ env.OPENSSL_PREV_VERSION }}-${{ runner.arch }} fail-on-cache-miss: true - name: 'cache wolfssl' if: ${{ contains(matrix.build.name, 'wolfssl') }} uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 - id: cache-wolfssl + id: cache1-wolfssl env: - cache-name: cache-wolfssl + cache-name: cache1-wolfssl with: - path: ~/wolfssl/build - key: ${{ runner.os }}-http3-build-${{ env.cache-name }}-${{ env.WOLFSSL_VERSION }} + path: ~/pkg/wolfssl + key: ${{ runner.os }}-http3-build-${{ env.cache-name }}-${{ env.WOLFSSL_VERSION }}-${{ runner.arch }} fail-on-cache-miss: true - name: 'cache nghttp3' uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 - id: cache-nghttp3 + id: cache1-nghttp3 env: - cache-name: cache-nghttp3 + cache-name: cache1-nghttp3 with: - path: ~/nghttp3/build - key: ${{ runner.os }}-http3-build-${{ env.cache-name }}-${{ env.NGHTTP3_VERSION }} + path: ~/pkg/nghttp3 + key: ${{ runner.os }}-http3-build-${{ env.cache-name }}-${{ env.NGHTTP3_VERSION }}-${{ runner.arch }} fail-on-cache-miss: true - name: 'cache ngtcp2' uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 - id: cache-ngtcp2 + id: cache1-ngtcp2 env: - cache-name: cache-ngtcp2 + cache-name: cache1-ngtcp2 with: - path: ~/ngtcp2/build + path: ~/pkg/ngtcp2 key: "${{ runner.os }}-http3-build-${{ env.cache-name }}-${{ env.NGTCP2_VERSION }}-${{ env.OPENSSL_VERSION }}-\ - ${{ env.LIBRESSL_VERSION }}-${{ env.AWSLC_VERSION }}-${{ env.NETTLE_VERSION }}-${{ env.GNUTLS_VERSION }}-${{ env.WOLFSSL_VERSION }}" + ${{ env.LIBRESSL_VERSION }}-${{ env.AWSLC_VERSION }}-${{ env.NETTLE_VERSION }}-${{ env.GNUTLS_VERSION }}-${{ env.WOLFSSL_VERSION }}-${{ runner.arch }}" fail-on-cache-miss: true - name: 'cache ngtcp2 boringssl' if: ${{ contains(matrix.build.name, 'boringssl') }} uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 - id: cache-ngtcp2-boringssl + id: cache1-ngtcp2-boringssl env: - cache-name: cache-ngtcp2-boringssl + cache-name: cache1-ngtcp2-boringssl with: - path: ~/ngtcp2-boringssl/build - key: ${{ runner.os }}-http3-build-${{ env.cache-name }}-${{ env.NGTCP2_VERSION }}-${{ env.BORINGSSL_VERSION }} + path: ~/pkg/ngtcp2-boringssl + key: ${{ runner.os }}-http3-build-${{ env.cache-name }}-${{ env.NGTCP2_VERSION }}-${{ env.BORINGSSL_VERSION }}-${{ runner.arch }} fail-on-cache-miss: true - name: 'cache ngtcp2 openssl-prev' if: ${{ contains(matrix.build.name, 'openssl-prev') }} uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 - id: cache-ngtcp2-openssl-prev + id: cache1-ngtcp2-openssl-prev env: - cache-name: cache-ngtcp2-openssl-prev + cache-name: cache1-ngtcp2-openssl-prev with: - path: ~/ngtcp2-openssl-prev/build - key: ${{ runner.os }}-http3-build-${{ env.cache-name }}-${{ env.NGTCP2_VERSION }}-${{ env.OPENSSL_PREV_VERSION }} + path: ~/pkg/ngtcp2-openssl-prev + key: ${{ runner.os }}-http3-build-${{ env.cache-name }}-${{ env.NGTCP2_VERSION }}-${{ env.OPENSSL_PREV_VERSION }}-${{ runner.arch }} fail-on-cache-miss: true - name: 'cache nghttp2' uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 - id: cache-nghttp2 + id: cache1-nghttp2 env: - cache-name: cache-nghttp2 + cache-name: cache1-nghttp2 with: - path: ~/nghttp2/build - key: ${{ runner.os }}-http3-build-${{ env.cache-name }}-${{ env.NGHTTP2_VERSION }}-${{ env.OPENSSL_VERSION }}-${{ env.NGTCP2_VERSION }}-${{ env.NGHTTP3_VERSION }} + path: ~/pkg/nghttp2 + key: ${{ runner.os }}-http3-build-${{ env.cache-name }}-${{ env.NGHTTP2_VERSION }}-${{ env.OPENSSL_VERSION }}-${{ env.NGTCP2_VERSION }}-${{ env.NGHTTP3_VERSION }}-${{ runner.arch }} fail-on-cache-miss: true - name: 'cache h2o' uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 - id: cache-h2o + id: cache1-h2o env: - cache-name: cache-h2o + cache-name: cache1-h2o with: - path: ~/h2o/build - key: ${{ runner.os }}-http3-build-${{ env.cache-name }}-${{ env.H2O_VERSION }}-${{ env.OPENSSL_PREV_VERSION }} + path: ~/pkg/h2o + key: ${{ runner.os }}-http3-build-${{ env.cache-name }}-${{ env.H2O_VERSION }}-${{ env.OPENSSL_PREV_VERSION }}-${{ runner.arch }} fail-on-cache-miss: true - name: 'cache quiche' if: ${{ contains(matrix.build.name, 'quiche') }} uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 - id: cache-quiche + id: cache1-quiche env: - cache-name: cache-quiche + cache-name: cache1-quiche with: path: ~/quiche - key: ${{ runner.os }}-http3-build-${{ env.cache-name }}-${{ env.QUICHE_VERSION }} + key: ${{ runner.os }}-http3-build-${{ env.cache-name }}-${{ env.QUICHE_VERSION }}-${{ runner.arch }} - name: 'build quiche and boringssl' - if: ${{ contains(matrix.build.name, 'quiche') && !steps.cache-quiche.outputs.cache-hit }} + if: ${{ contains(matrix.build.name, 'quiche') && !steps.cache1-quiche.outputs.cache-hit }} run: | cd ~ git clone --quiet --depth 1 --branch "${QUICHE_VERSION}" --recursive https://github.com/cloudflare/quiche cd quiche - cargo build -v --package quiche --release --features ffi,pkg-config-meta,qlog --verbose + cargo build --package quiche --release --features ffi,pkg-config-meta,qlog -vv ln -s libquiche.so target/release/libquiche.so.0 cd .. mkdir -p quiche/boringssl/lib @@ -768,10 +773,8 @@ jobs: rm -rf quiche/target/release/deps du -t 4MB quiche - # include dir - # /home/runner/quiche/boringssl/include - # lib dir - # /home/runner/quiche/boringssl/lib + # include dir: /home/runner/quiche/boringssl/include + # lib dir: /home/runner/quiche/boringssl/lib - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: @@ -799,17 +802,17 @@ jobs: -DCMAKE_C_COMPILER_TARGET="$(uname -m)-unknown-linux-gnu" -DBUILD_STATIC_LIBS=ON \ -DCURL_WERROR=ON -DENABLE_DEBUG=ON \ -DCURL_USE_LIBUV=ON -DCURL_ENABLE_NTLM=ON \ - -DTEST_NGHTTPX=/home/runner/nghttp2/build/bin/nghttpx \ - -DH2O=/home/runner/h2o/build/bin/h2o \ - -DHTTPD_NGHTTPX=/home/runner/nghttp2/build/bin/nghttpx \ + -DTEST_NGHTTPX=/home/runner/pkg/nghttp2/bin/nghttpx \ + -DH2O=/home/runner/pkg/h2o/bin/h2o \ + -DHTTPD_NGHTTPX=/home/runner/pkg/nghttp2/bin/nghttpx \ ${MATRIX_GENERATE} ${options} else [[ "${MATRIX_NAME}" = *'boringssl'* ]] && export CPPFLAGS="-DCURL_BORINGSSL_VERSION=\\\"${BORINGSSL_VERSION}\\\"" mkdir bld && cd bld && ../configure --enable-warnings --enable-werror --enable-debug --disable-static \ --disable-dependency-tracking --enable-option-checking=fatal \ --with-libuv --enable-ntlm \ - --with-test-h2o=/home/runner/h2o/build/bin/h2o \ - --with-test-nghttpx=/home/runner/nghttp2/build/bin/nghttpx \ + --with-test-h2o=/home/runner/pkg/h2o/bin/h2o \ + --with-test-nghttpx=/home/runner/pkg/nghttp2/bin/nghttpx \ ${MATRIX_CONFIGURE} fi From 7da7436d24720ae95bb6162eb8eb8adba78bcfcd Mon Sep 17 00:00:00 2001 From: Viktor Szakats Date: Fri, 2 Oct 2026 22:27:21 +0200 Subject: [PATCH 368/481] GHA/codeql: switch Linux job to arm64 For better efficiency, possibly faster runs. Closes #23240 --- .github/workflows/codeql.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 7fc2a26b0b9a..6ffa281e2467 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -63,7 +63,7 @@ jobs: c: if: ${{ github.repository_owner == 'curl' || github.event_name != 'schedule' }} name: 'C' - runs-on: ${{ matrix.platform == 'Linux' && 'ubuntu-26.04' || 'windows-2022' }} + runs-on: ${{ matrix.platform == 'Linux' && 'ubuntu-26.04-arm' || 'windows-2022' }} permissions: security-events: write # To create/update security events strategy: From a75c217404a0b97b54b220660892d0fec8e7536e Mon Sep 17 00:00:00 2001 From: Viktor Szakats Date: Fri, 2 Oct 2026 21:34:23 +0200 Subject: [PATCH 369/481] spacecheck.pl: ensure files do not have their executable bit set Make sure committed files do not have their executable bit set, scripts excepted. Closes #23239 --- scripts/spacecheck.pl | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/scripts/spacecheck.pl b/scripts/spacecheck.pl index 4177a7a2be57..4f1e2afa361b 100755 --- a/scripts/spacecheck.pl +++ b/scripts/spacecheck.pl @@ -28,6 +28,12 @@ use File::Basename; +my @executable = ( + '\.(pl|py|sh)$', + '^\.github/scripts/', + '^scripts/', +); + my @tabs = ( '^m4/zz40-xc-ovr\.m4$', 'Makefile\.(am|example)$', @@ -116,6 +122,13 @@ sub eol_detect { my @err = (); + if(!fn_match($filename, @executable)) { + my $mode = (stat($filename))[2]; + if($mode & 0111) { + push @err, sprintf('file has executable bit(s) set: %o', $mode); + } + } + if(length($filename) > $max_path_len) { push @err, sprintf('long (%d > %d) path', length($filename), $max_path_len); } From 52d6355bb81bf8435a72f44b12c76bd5c76eba7e Mon Sep 17 00:00:00 2001 From: Viktor Szakats Date: Sat, 3 Oct 2026 00:15:10 +0200 Subject: [PATCH 370/481] GHA/windows: replace `diffie-hellman-group-exchange-sha256` with `diffie-hellman-group16-sha512` To use a superior fallback KEX. Refs: https://datatracker.ietf.org/doc/html/rfc9142 https://github.blog/changelog/2026-09-22-security-improvements-for-ssh/ Follow-up to 41f3f94d9191f2454ac12ae9130e44b78c55e1fd #21438 Closes #23242 --- .github/workflows/windows.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 230454a687c0..6a09674d81cd 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -538,7 +538,7 @@ jobs: if [[ "${MATRIX_INSTALL}" = *'libssh2-wincng'* ]]; then TFLAGS+=' ~SCP ~SFTP' # Flaky: `-8, Unable to exchange encryption keys`. https://github.com/libssh2/libssh2/issues/804 unset CURL_TEST_SSH_KEYALGO # libssh2 built with WinCNG does not support ssh-ed25519 hostkeys - export CURL_TEST_SSH_ENABLE_KEX=diffie-hellman-group-exchange-sha256 + export CURL_TEST_SSH_ENABLE_KEX=diffie-hellman-group16-sha512 fi if [[ "${MATRIX_CONFIG} " = *'-DCURL_USE_LIBSSH=ON'* && \ "${MATRIX_ENV}" = *'x86_64'* ]]; then @@ -1216,7 +1216,7 @@ jobs: "${MATRIX_INSTALL_VCPKG}" = *'libssh2[core,zlib]'* ]]; then TFLAGS+=' ~SCP ~SFTP' # Flaky: `-8, Unable to exchange encryption keys`. https://github.com/libssh2/libssh2/issues/804 unset CURL_TEST_SSH_KEYALGO # libssh2 built with WinCNG does not support ssh-ed25519 hostkeys - export CURL_TEST_SSH_ENABLE_KEX=diffie-hellman-group-exchange-sha256 + export CURL_TEST_SSH_ENABLE_KEX=diffie-hellman-group16-sha512 fi if [ -n "${MATRIX_OPENSSH}" ]; then # OpenSSH-Windows TFLAGS+=' ~601 ~603 ~617 ~619 ~621 ~641 ~665 ~2004' # SCP From 2f642a75bbcbe4a58273998e4767c1e1b5b34e26 Mon Sep 17 00:00:00 2001 From: Viktor Szakats Date: Sat, 3 Oct 2026 09:33:44 +0200 Subject: [PATCH 371/481] tidy-up: drop broken Unicode codepoint links Also convert `U+` to uppercase for consistency, where missing. Fixing: ``` Checked 434 URLs 1 URLs had problems: scripts/spacecheck.pl:61 ERROR links to missing URL https://codepoints.net/U+00F6 ``` Ref: https://github.com/curl/curl/actions/runs/34190347543/job/101946950515#step:4:445 Closes #23244 --- scripts/spacecheck.pl | 2 +- tests/libtest/lib1560.c | 20 ++++++++++---------- tests/unit/unit1307.c | 2 +- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/scripts/spacecheck.pl b/scripts/spacecheck.pl index 4f1e2afa361b..af79206179fe 100755 --- a/scripts/spacecheck.pl +++ b/scripts/spacecheck.pl @@ -64,7 +64,7 @@ ); my @non_ascii_allowed = ( - '\xC3\xB6', # UTF-8 for https://codepoints.net/U+00F6 LATIN SMALL LETTER O WITH DIAERESIS + '\xC3\xB6', # UTF-8 for U+00F6 LATIN SMALL LETTER O WITH DIAERESIS ); my $non_ascii_allowed = join(', ', @non_ascii_allowed); diff --git a/tests/libtest/lib1560.c b/tests/libtest/lib1560.c index 65d243eff441..9f9073569dab 100644 --- a/tests/libtest/lib1560.c +++ b/tests/libtest/lib1560.c @@ -313,9 +313,9 @@ static const struct testcase get_parts_list[] = { 0, CURLU_URLDECODE, CURLUE_OK }, #ifdef USE_IDN /* https://sv.wikipedia.org/wiki/R%c3%a4ksm%c3%b6rg%c3%a5s - https://codepoints.net/U+00E4 Latin Small Letter A with Diaeresis - https://codepoints.net/U+00F6 Latin Small Letter O with Diaeresis - https://codepoints.net/U+00E5 Latin Small Letter A with Ring Above + U+00E4 Latin Small Letter A with Diaeresis + U+00F6 Latin Small Letter O with Diaeresis + U+00E5 Latin Small Letter A with Ring Above */ { "https://r\xc3\xa4ksm\xc3\xb6rg\xc3\xa5s.se", "https | [11] | [12] | [13] | xn--rksmrgs-5wao1o.se | " @@ -334,13 +334,13 @@ static const struct testcase get_parts_list[] = { "https | [11] | [12] | [13] | [30] | [15] | / | [16] | [17]", 0, CURLU_PUNYCODE, CURLUE_OK }, #endif - /* https://codepoints.net/U+2102 Double-Struck Capital C - https://codepoints.net/U+1d64 Latin Subscript Small Letter U - https://codepoints.net/U+24c7 Circled Latin Capital Letter R - https://codepoints.net/U+2112 Script Capital L - https://codepoints.net/U+3002 Ideographic Full Stop - https://codepoints.net/U+1d412 Mathematical Bold Capital S - https://codepoints.net/U+1f134 Squared Latin Capital Letter E + /* U+2102 Double-Struck Capital C + U+1D64 Latin Subscript Small Letter U + U+24C7 Circled Latin Capital Letter R + U+2112 Script Capital L + U+3002 Ideographic Full Stop + U+1D412 Mathematical Bold Capital S + U+1F134 Squared Latin Capital Letter E */ {"https://" "%e2%84%82%e1%b5%a4%e2%93%87%e2%84%92%e3%80%82%f0%9d%90%92%f0%9f%84%b4", diff --git a/tests/unit/unit1307.c b/tests/unit/unit1307.c index 9ed570967589..afd479c7deec 100644 --- a/tests/unit/unit1307.c +++ b/tests/unit/unit1307.c @@ -262,7 +262,7 @@ static CURLcode test_unit1307(const char *arg) { "?*?*?.?", "abcdef.c", MATCH }, { "?*?*?.?", "abcdef.cd", NOMATCH }, - /* https://codepoints.net/U+00E4 Latin Small Letter A with Diaeresis */ + /* U+00E4 Latin Small Letter A with Diaeresis */ { "Lindm\xc3\xa4tarv", "Lindm\xc3\xa4tarv", MATCH }, { "", "", MATCH}, From 21983c2fca1e4ecf3393e94455b343d082b6f1c9 Mon Sep 17 00:00:00 2001 From: Viktor Szakats Date: Sat, 3 Oct 2026 09:44:07 +0200 Subject: [PATCH 372/481] IPFS.md: replace sunset link, allowlist another one to avoid `mdlinkcheck` hang The link check hung on the old link (tarpitted maybe?): https://dweb.link/ipfs/bafybeigagd5nmnn2iys2f3doro7ydrevyr2mzarwidgadawmamiteydbzi With that replaced, also on this: https://ipfs.io/ipfs/bafybeigagd5nmnn2iys2f3doro7ydrevyr2mzarwidgadawmamiteydbzi Also: - mdlinkcheck: allowlist the link above to avoid the hang after receiving HTTP 429 'Too Many Requests' response. This happened with curl 8.5.0 and 8.18.0, with both H2 and HTTP 1.1. Refs: https://gatewaychanges.ipfs.io/#/ipfs/bafybeigagd5nmnn2iys2f3doro7ydrevyr2mzarwidgadawmamiteydbzi https://github.com/ipfs/ipfs-docs/blob/main/docs/how-to/websites-on-ipfs/custom-domains.md Closes #23245 --- docs/IPFS.md | 6 +++--- scripts/mdlinkcheck | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/IPFS.md b/docs/IPFS.md index 2cf64543b93f..612507e23034 100644 --- a/docs/IPFS.md +++ b/docs/IPFS.md @@ -109,15 +109,15 @@ requests to [subdomain based](https://docs.ipfs.tech/how-to/address-ipfs-on-web/#subdomain-gateway) ones. A request using: - curl ipfs://bafybeigagd5nmnn2iys2f3doro7ydrevyr2mzarwidgadawmamiteydbzi --ipfs-gateway https://dweb.link + curl ipfs://bafybeigagd5nmnn2iys2f3doro7ydrevyr2mzarwidgadawmamiteydbzi --ipfs-gateway https://inbrowser.link/ Which would be translated to: - https://dweb.link/ipfs/bafybeigagd5nmnn2iys2f3doro7ydrevyr2mzarwidgadawmamiteydbzi + https://inbrowser.link/ipfs/bafybeigagd5nmnn2iys2f3doro7ydrevyr2mzarwidgadawmamiteydbzi redirects to: - https://bafybeigagd5nmnn2iys2f3doro7ydrevyr2mzarwidgadawmamiteydbzi.ipfs.dweb.link + https://bafybeigagd5nmnn2iys2f3doro7ydrevyr2mzarwidgadawmamiteydbzi.ipfs.inbrowser.link/ If you trust this behavior from your gateway of choice then passing the `-L` option follows the redirect. diff --git a/scripts/mdlinkcheck b/scripts/mdlinkcheck index 4d30e90d392e..89dc3c2485f5 100755 --- a/scripts/mdlinkcheck +++ b/scripts/mdlinkcheck @@ -78,6 +78,7 @@ my %whitelist = ( 'https://github.com/curl/curl-www' => 1, 'https://github.com/curl/curl/wcurl' => 1, + 'https://ipfs.io/ipfs/bafybeigagd5nmnn2iys2f3doro7ydrevyr2mzarwidgadawmamiteydbzi' => 1, # hangs after 429 ); my %url; From 70239645940747a03ea51cecc4ec3b8abfda0414 Mon Sep 17 00:00:00 2001 From: Jorge Rocamora <33847633+aeroyorch@users.noreply.github.com> Date: Sun, 20 Sep 2026 12:59:47 +0200 Subject: [PATCH 373/481] docs: document and align the blocking situations post-transfer and shutdown waits Closes #23006 --- docs/TODO.md | 8 +++++++- docs/libcurl/libcurl-multi.md | 4 ++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/docs/TODO.md b/docs/TODO.md index 859e684cb551..815d84353008 100644 --- a/docs/TODO.md +++ b/docs/TODO.md @@ -170,10 +170,16 @@ Make sure we do not ever loop because of non-blocking sockets returning - TELNET transfers - GSSAPI authentication for FTP transfers - The "DONE" operation (post transfer protocol-specific actions) for the -protocols SFTP, SMTP, FTP. Fixing `multi_done()` for this is a worthy task. +protocols SFTP, SCP, SMTP, IMAP, FTP. Fixing `multi_done()` for this is a +worthy task. +- The protocol disconnect handlers for FTP, IMAP, POP3, SMTP, SCP and SFTP, +when a connection is closed instead of kept for reuse. - `curl_multi_remove_handle()` for any of the above. - Calling `curl_ws_send()` from a callback +Several of these are also documented in the BLOCKING section +of [`libcurl-multi`](https://curl.se/libcurl/c/libcurl-multi.html). + ## Better support for same name resolves If a name resolve has been initiated for a given name and a second easy handle diff --git a/docs/libcurl/libcurl-multi.md b/docs/libcurl/libcurl-multi.md index da1331fae82c..0d56dac769bd 100644 --- a/docs/libcurl/libcurl-multi.md +++ b/docs/libcurl/libcurl-multi.md @@ -178,3 +178,7 @@ the future, you should be aware of the following current restrictions: - Name resolves unless the c-ares or threaded-resolver backends are used - `file://` transfers - TELNET transfers +- FTP, IMAP, SMTP, SCP and SFTP wait for the final server response before the +transfer is reported as done +- FTP, IMAP, POP3, SMTP, SCP and SFTP can block during protocol-specific +shutdown when a connection is closed instead of kept for reuse From 2678ea9fb4120a48b46153e8b6aecdcf8562522e Mon Sep 17 00:00:00 2001 From: Viktor Szakats Date: Sun, 4 Oct 2026 01:35:02 +0200 Subject: [PATCH 374/481] mdlinkcheck: pass `--retry-max-time` to avoid server-controlled long retry times A server returning `retry-after:` header with a high value could make the job effectively hang a timeout. Fix it by limiting the retry delay. Seen with `retry-after: 900` returned by `https://ipfs.io/ipfs/bafybeigagd5nmnn2iys2f3doro7ydrevyr2mzarwidgadawmamiteydbzi` and `dweb.link` URLs. Suggested-by: Jay Satiro Bug: https://github.com/curl/curl/pull/23245#issuecomment-5967135931 Follow-up to 21983c2fca1e4ecf3393e94455b343d082b6f1c9 #23245 Closes #23248 --- scripts/mdlinkcheck | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/scripts/mdlinkcheck b/scripts/mdlinkcheck index 89dc3c2485f5..3da3faa48246 100755 --- a/scripts/mdlinkcheck +++ b/scripts/mdlinkcheck @@ -78,7 +78,6 @@ my %whitelist = ( 'https://github.com/curl/curl-www' => 1, 'https://github.com/curl/curl/wcurl' => 1, - 'https://ipfs.io/ipfs/bafybeigagd5nmnn2iys2f3doro7ydrevyr2mzarwidgadawmamiteydbzi' => 1, # hangs after 429 ); my %url; @@ -190,7 +189,7 @@ sub checkurl { $url =~ s/\+/%2B/g; my @content; - if(open(my $fh, '-|', 'curl', '-ILfsm10', '--retry', '2', '--retry-delay', '5', + if(open(my $fh, '-|', 'curl', '-ILfsm10', '--retry', '2', '--retry-delay', '5', '--retry-max-time', '60', '-A', 'Mozilla/curl.se link-probe', $url)) { @content = <$fh>; close $fh; From 10a7e1cf8495eeab2c2389faffb7afb65b2d7f9e Mon Sep 17 00:00:00 2001 From: Viktor Szakats Date: Fri, 2 Oct 2026 23:26:12 +0200 Subject: [PATCH 375/481] GHA: move 7 checker jobs to arm64 - checkdocs: pyspelling (50s -> 36s) - CodeQL: GHA and Python (1m49s -> 1m5s) - configure-vs-cmake: Linux (45s -> 39s) - configure-vs-cmake: Windows (1m3s -> 49s) - distcheck: CM in-tree !perl (33s -> 24s) - distcheck: CM out-of-tree !perl (24s -> 17s) - distcheck: reproducible releases (5m8s -> 4m8s) Smaller differences may not be consistent across all runs. Intel and. arm64 are pretty close for some tasks, and arm64 clearly faster in some others. In general, with current machine loads, arm64 is generally faster. Also, compared to earlier this year, arm64 now uses the same Ubuntu mirrors as Intel, so this is no longer causing job time differences. Also: - cmp-config.pl: adjust exception list for arm64 targets. Closes #23241 --- .github/scripts/cmp-config.pl | 1 + .github/workflows/checkdocs.yml | 2 +- .github/workflows/codeql.yml | 2 +- .github/workflows/configure-vs-cmake.yml | 4 ++-- .github/workflows/distcheck.yml | 6 +++--- 5 files changed, 8 insertions(+), 7 deletions(-) diff --git a/.github/scripts/cmp-config.pl b/.github/scripts/cmp-config.pl index a0d33498a75f..b11cd00e1349 100755 --- a/.github/scripts/cmp-config.pl +++ b/.github/scripts/cmp-config.pl @@ -38,6 +38,7 @@ my %remove = ( '#define CURL_EXTERN_SYMBOL' => 1, '#define CURL_OS "Linux"' => 1, + '#define CURL_OS "aarch64-unknown-linux-gnu"' => 1, '#define CURL_OS "x86_64-pc-linux-gnu"' => 1, '#define GETHOSTNAME_TYPE_ARG2 int' => 1, '#define GETHOSTNAME_TYPE_ARG2 size_t' => 1, diff --git a/.github/workflows/checkdocs.yml b/.github/workflows/checkdocs.yml index c1c152fbed03..d6031d59598a 100644 --- a/.github/workflows/checkdocs.yml +++ b/.github/workflows/checkdocs.yml @@ -100,7 +100,7 @@ jobs: pyspelling: name: 'pyspelling' - runs-on: ubuntu-26.04 + runs-on: ubuntu-26.04-arm steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 6ffa281e2467..df04be0ebc85 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -43,7 +43,7 @@ jobs: gha_python: if: ${{ github.repository_owner == 'curl' || github.event_name != 'schedule' }} name: 'GHA and Python' - runs-on: ubuntu-26.04 + runs-on: ubuntu-26.04-arm permissions: security-events: write # To create/update security events steps: diff --git a/.github/workflows/configure-vs-cmake.yml b/.github/workflows/configure-vs-cmake.yml index 3f2e691261de..3620e29d16dc 100644 --- a/.github/workflows/configure-vs-cmake.yml +++ b/.github/workflows/configure-vs-cmake.yml @@ -46,7 +46,7 @@ env: jobs: check-linux: name: 'Linux' - runs-on: ubuntu-latest + runs-on: ubuntu-26.04-arm steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: @@ -141,7 +141,7 @@ jobs: check-windows: name: 'Windows' - runs-on: ubuntu-26.04 + runs-on: ubuntu-26.04-arm env: TRIPLET: 'x86_64-w64-mingw32' steps: diff --git a/.github/workflows/distcheck.yml b/.github/workflows/distcheck.yml index a0f0f14ed9b6..cec6c4ee3b12 100644 --- a/.github/workflows/distcheck.yml +++ b/.github/workflows/distcheck.yml @@ -169,7 +169,7 @@ jobs: verify-out-of-tree-cmake: name: 'CM out-of-tree !perl' - runs-on: ubuntu-latest + runs-on: ubuntu-26.04-arm timeout-minutes: 5 needs: maketgz-and-verify-in-tree steps: @@ -191,7 +191,7 @@ jobs: verify-in-tree-cmake: name: 'CM in-tree !perl' - runs-on: ubuntu-latest + runs-on: ubuntu-26.04-arm timeout-minutes: 5 needs: maketgz-and-verify-in-tree steps: @@ -230,7 +230,7 @@ jobs: reproducible-releases: name: 'reproducible releases' - runs-on: ubuntu-latest + runs-on: ubuntu-26.04-arm timeout-minutes: 10 steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 From 61a31e02be295596a0a20b692df585fd498bdb7b Mon Sep 17 00:00:00 2001 From: Daniel Stenberg Date: Sat, 3 Oct 2026 23:59:19 +0200 Subject: [PATCH 376/481] http: simplify Curl_http_follow by introducing a few helper functions. This function was the most complex function in curl before this change (50). Closes #23247 --- lib/http.c | 269 ++++++++++++++++++++++++++++------------------------- 1 file changed, 144 insertions(+), 125 deletions(-) diff --git a/lib/http.c b/lib/http.c index 0f201c6c509e..21dbc120a641 100644 --- a/lib/http.c +++ b/lib/http.c @@ -1177,73 +1177,47 @@ static void http_switch_to_get(struct Curl_easy *data, int code) (data)->state.httpreq == HTTPREQ_POST_FORM || \ (data)->state.httpreq == HTTPREQ_POST_MIME) -CURLcode Curl_http_follow(struct Curl_easy *data, const char *newurl, - followtype type) +static CURLcode set_auto_referer(struct Curl_easy *data) { - bool disallowport = FALSE; - bool reachedmax = FALSE; - char *follow_url = NULL; + CURLU *u; + char *referer = NULL; CURLUcode uc; - CURLcode rewind_result; - bool switch_to_get = FALSE; - - DEBUGASSERT(type != FOLLOW_NONE); - - if(type != FOLLOW_FAKE) - data->state.requests++; /* count all real follows */ - if(type == FOLLOW_REDIR) { - if((data->set.maxredirs != -1) && - (data->state.followlocation >= data->set.maxredirs)) { - reachedmax = TRUE; - type = FOLLOW_FAKE; /* switch to fake to store the would-be-redirected - to URL */ - } - else { - data->state.followlocation++; /* count redirect-followings, including - auth reloads */ - - if(data->set.http_auto_referer) { - CURLU *u; - char *referer = NULL; - /* We are asked to automatically set the previous URL as the referer - when we get the next URL. We pick the ->url field, which may or may - not be 100% correct */ - Curl_bufref_free(&data->state.referer); + /* We are asked to automatically set the previous URL as the referer + when we get the next URL. We pick the ->url field, which may or may + not be 100% correct */ + Curl_bufref_free(&data->state.referer); - /* Make a copy of the URL without credentials and fragment */ - u = curl_url(); - if(!u) - return CURLE_OUT_OF_MEMORY; + /* Make a copy of the URL without credentials and fragment */ + u = curl_url(); + if(!u) + return CURLE_OUT_OF_MEMORY; - uc = curl_url_set(u, CURLUPART_URL, - Curl_bufref_ptr(&data->state.url), 0); - if(!uc) - uc = curl_url_set(u, CURLUPART_FRAGMENT, NULL, 0); - if(!uc) - uc = curl_url_set(u, CURLUPART_USER, NULL, 0); - if(!uc) - uc = curl_url_set(u, CURLUPART_PASSWORD, NULL, 0); - if(!uc) - uc = curl_url_get(u, CURLUPART_URL, &referer, 0); + uc = curl_url_set(u, CURLUPART_URL, + Curl_bufref_ptr(&data->state.url), 0); + if(!uc) + uc = curl_url_set(u, CURLUPART_FRAGMENT, NULL, 0); + if(!uc) + uc = curl_url_set(u, CURLUPART_USER, NULL, 0); + if(!uc) + uc = curl_url_set(u, CURLUPART_PASSWORD, NULL, 0); + if(!uc) + uc = curl_url_get(u, CURLUPART_URL, &referer, 0); - curl_url_cleanup(u); + curl_url_cleanup(u); - if(uc || !referer) - return CURLE_OUT_OF_MEMORY; + if(uc || !referer) + return CURLE_OUT_OF_MEMORY; - Curl_bufref_set(&data->state.referer, referer, 0, curl_free); - } - } - } + Curl_bufref_set(&data->state.referer, referer, 0, curl_free); + return CURLE_OK; +} - if((type != FOLLOW_RETRY) && - (data->req.httpcode != 401) && (data->req.httpcode != 407) && - Curl_is_absolute_url(newurl, NULL, 0, FALSE)) { - /* If this is not redirect due to a 401 or 407 response and an absolute - URL: do not allow a custom port number */ - disallowport = TRUE; - } +static CURLcode follow_parse_url(struct Curl_easy *data, const char *newurl, + followtype type, char **follow_urlp, + CURLUcode *ucp) +{ + CURLUcode uc; DEBUGASSERT(data->state.uh); uc = curl_url_set(data->state.uh, CURLUPART_URL, newurl, (unsigned int) @@ -1251,6 +1225,7 @@ CURLcode Curl_http_follow(struct Curl_easy *data, const char *newurl, ((type == FOLLOW_REDIR) ? CURLU_URLENCODE : 0) | CURLU_ALLOW_SPACE | (data->set.path_as_is ? CURLU_PATH_AS_IS : 0))); + *ucp = uc; if(uc) { if((uc == CURLUE_OUT_OF_MEMORY) || (type != FOLLOW_FAKE)) { failf(data, "The redirect target URL could not be parsed: %s", @@ -1260,8 +1235,8 @@ CURLcode Curl_http_follow(struct Curl_easy *data, const char *newurl, /* the URL could not be parsed for some reason, but since this is FAKE mode, duplicate the field as-is */ - follow_url = curlx_strdup(newurl); - if(!follow_url) + *follow_urlp = curlx_strdup(newurl); + if(!*follow_urlp) return CURLE_OUT_OF_MEMORY; } else { @@ -1272,7 +1247,7 @@ CURLcode Curl_http_follow(struct Curl_easy *data, const char *newurl, Curl_bufref_ptr(&data->state.url), CURLU_URLENCODE | CURLU_ALLOW_SPACE); if(!uc) - uc = curl_url_get(data->state.uh, CURLUPART_URL, &follow_url, 0); + uc = curl_url_get(data->state.uh, CURLUPART_URL, follow_urlp, 0); if(uc) { curl_url_cleanup(u); return Curl_uc_to_curlcode(uc); @@ -1289,73 +1264,50 @@ CURLcode Curl_http_follow(struct Curl_easy *data, const char *newurl, curl_url_cleanup(u); #endif } - DEBUGASSERT(follow_url); + return CURLE_OK; +} - if(type == FOLLOW_FAKE) { - /* we are only figuring out the new URL if we would have followed locations - but now we are done so we can get out! */ - if(!uc) { - CURLU *u = curl_url(); - char *nocred = NULL; +static CURLcode follow_fake(struct Curl_easy *data, char *follow_url, + CURLUcode uc, bool reachedmax) +{ + /* we are only figuring out the new URL if we would have followed locations + but now we are done so we can get out! */ + if(!uc) { + CURLU *u = curl_url(); + char *nocred = NULL; - if(!u) { - curlx_free(follow_url); - return CURLE_OUT_OF_MEMORY; - } - if(!curl_url_set(u, CURLUPART_URL, follow_url, - CURLU_NON_SUPPORT_SCHEME | - (data->set.path_as_is ? CURLU_PATH_AS_IS : 0)) && - !curl_url_set(u, CURLUPART_USER, NULL, 0) && - !curl_url_set(u, CURLUPART_PASSWORD, NULL, 0)) - (void)curl_url_get(u, CURLUPART_URL, &nocred, CURLU_GET_EMPTY); - curl_url_cleanup(u); - if(nocred) { - curlx_free(follow_url); - follow_url = nocred; - } + if(!u) { + curlx_free(follow_url); + return CURLE_OUT_OF_MEMORY; } - data->info.wouldredirect = follow_url; - - if(reachedmax) { - failf(data, "Maximum (%d) redirects followed", data->set.maxredirs); - return CURLE_TOO_MANY_REDIRECTS; + if(!curl_url_set(u, CURLUPART_URL, follow_url, + CURLU_NON_SUPPORT_SCHEME | + (data->set.path_as_is ? CURLU_PATH_AS_IS : 0)) && + !curl_url_set(u, CURLUPART_USER, NULL, 0) && + !curl_url_set(u, CURLUPART_PASSWORD, NULL, 0)) + (void)curl_url_get(u, CURLUPART_URL, &nocred, CURLU_GET_EMPTY); + curl_url_cleanup(u); + if(nocred) { + curlx_free(follow_url); + follow_url = nocred; } - return CURLE_OK; } + data->info.wouldredirect = follow_url; - if(disallowport) - data->state.allow_port = FALSE; - - Curl_bufref_set(&data->state.url, follow_url, 0, curl_free); - rewind_result = Curl_req_soft_reset(&data->req, data); - infof(data, "Issue another request to this URL: '%s'", follow_url); - if((data->set.http_follow_mode == CURLFOLLOW_FIRSTONLY) && - !data->state.http_ignorecustom && - CURL_EASY_STR(data, STRING_CUSTOMREQUEST)) { - data->state.http_ignorecustom = TRUE; - infof(data, "Drop custom request method for next request"); + if(reachedmax) { + failf(data, "Maximum (%d) redirects followed", data->set.maxredirs); + return CURLE_TOO_MANY_REDIRECTS; } + return CURLE_OK; +} +static bool follow_switch_get(struct Curl_easy *data) +{ /* - * We get here when the HTTP code is 300-399 (and 401). We need to perform - * differently based on exactly what return code there was. - * - * News from 7.10.6: we can also get here on a 401 or 407, in case we act on - * an HTTP (proxy-) authentication scheme other than Basic. + * We get here when the HTTP code is 300-399 and 401 or 407. We need to + * perform differently based on exactly what return code there was. */ switch(data->info.httpcode) { - /* 401 - Act on a WWW-Authenticate, we keep on moving and do the - Authorization: XXXX header in the HTTP request code snippet */ - /* 407 - Act on a Proxy-Authenticate, we keep on moving and do the - Proxy-Authorization: XXXX header in the HTTP request code snippet */ - /* 300 - Multiple Choices */ - /* 306 - Not used */ - /* 307 - Temporary Redirect */ - default: /* for all above (and the unknown ones) */ - /* Some codes are explicitly mentioned since I have checked RFC2616 and - * they seem to be OK to POST to. - */ - break; case 301: /* Moved Permanently */ /* (quote from RFC7231, section 6.4.2) * @@ -1375,7 +1327,7 @@ CURLcode Curl_http_follow(struct Curl_easy *data, const char *newurl, */ if(HTTPREQ_IS_POST(data) && !data->set.post301) { http_switch_to_get(data, 301); - switch_to_get = TRUE; + return TRUE; } break; case 302: /* Found */ @@ -1397,10 +1349,9 @@ CURLcode Curl_http_follow(struct Curl_easy *data, const char *newurl, */ if(HTTPREQ_IS_POST(data) && !data->set.post302) { http_switch_to_get(data, 302); - switch_to_get = TRUE; + return TRUE; } break; - case 303: /* See Other */ /* 'See Other' location is not the resource but a substitute for the * resource. In this case we switch the method to GET/HEAD, unless the @@ -1408,24 +1359,92 @@ CURLcode Curl_http_follow(struct Curl_easy *data, const char *newurl, */ if(!HTTPREQ_IS_POST(data) || !data->set.post303) { http_switch_to_get(data, 303); - switch_to_get = TRUE; + return TRUE; } break; - case 304: /* Not Modified */ + /* 304 means we did a conditional request and it was "Not modified". * We should not get any Location: header in this response! */ - break; - case 305: /* Use Proxy */ - /* (quote from RFC2616, section 10.3.6): + + /* 305 (quote from RFC2616, section 10.3.6): * "The requested resource MUST be accessed through the proxy given * by the Location field. The Location field gives the URI of the * proxy. The recipient is expected to repeat this single request * via the proxy. 305 responses MUST only be generated by origin * servers." */ + default: break; } + return FALSE; +} + +CURLcode Curl_http_follow(struct Curl_easy *data, const char *newurl, + followtype type) +{ + bool disallowport = FALSE; + bool reachedmax = FALSE; + char *follow_url = NULL; + CURLUcode uc; + CURLcode result; + CURLcode rewind_result; + bool switch_to_get; + + DEBUGASSERT(type != FOLLOW_NONE); + + if(type != FOLLOW_FAKE) + data->state.requests++; /* count all real follows */ + if(type == FOLLOW_REDIR) { + if((data->set.maxredirs != -1) && + (data->state.followlocation >= data->set.maxredirs)) { + reachedmax = TRUE; + type = FOLLOW_FAKE; /* switch to fake to store the would-be-redirected + to URL */ + } + else { + data->state.followlocation++; /* count redirect-followings, including + auth reloads */ + + if(data->set.http_auto_referer) { + result = set_auto_referer(data); + if(result) + return result; + } + } + } + + if((type != FOLLOW_RETRY) && + (data->req.httpcode != 401) && (data->req.httpcode != 407) && + Curl_is_absolute_url(newurl, NULL, 0, FALSE)) { + /* If this is not redirect due to a 401 or 407 response and an absolute + URL: do not allow a custom port number */ + disallowport = TRUE; + } + + result = follow_parse_url(data, newurl, type, &follow_url, &uc); + if(result) + return result; + + DEBUGASSERT(follow_url); + + if(type == FOLLOW_FAKE) + return follow_fake(data, follow_url, uc, reachedmax); + + if(disallowport) + data->state.allow_port = FALSE; + + Curl_bufref_set(&data->state.url, follow_url, 0, curl_free); + rewind_result = Curl_req_soft_reset(&data->req, data); + infof(data, "Issue another request to this URL: '%s'", follow_url); + if((data->set.http_follow_mode == CURLFOLLOW_FIRSTONLY) && + !data->state.http_ignorecustom && + CURL_EASY_STR(data, STRING_CUSTOMREQUEST)) { + data->state.http_ignorecustom = TRUE; + infof(data, "Drop custom request method for next request"); + } + + switch_to_get = follow_switch_get(data); /* When rewind of upload data failed and we are not switching to GET, * we need to fail the follow, as we cannot send the data again. */ From 8680115ae687bcdce52231bb8bf49e1a60ff40fc Mon Sep 17 00:00:00 2001 From: Viktor Szakats Date: Sun, 4 Oct 2026 14:05:58 +0200 Subject: [PATCH 377/481] GHA/windows: bump dl-mingw gcc-16 toolchain Ref: https://github.com/skeeto/w64devkit/releases/tag/v2.10.0 Closes #23251 --- .github/workflows/windows.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 6a09674d81cd..d7fab768f275 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -603,9 +603,9 @@ jobs: sys: 'mingw64' dir: 'w64devkit' env: 'x86_64' - ver: '16.1.0' - url: 'https://github.com/skeeto/w64devkit/releases/download/v2.8.0/w64devkit-x64-2.8.0.7z.exe' - SHA256: 6252bf34fe2231a55ac7f03d482b36d2c7c58697990551bba508102cfb3f342e + ver: '16.2.0' + url: 'https://github.com/skeeto/w64devkit/releases/download/v2.10.0/w64devkit-x64-2.10.0.7z.exe' + SHA256: 18d0a4c71a166f8401ab6305781bec5882b40b5e06ba9807c61cb5f3b3c6325e config: '-DENABLE_DEBUG=ON -DBUILD_SHARED_LIBS=OFF -DCURL_USE_SCHANNEL=ON -DCURL_USE_LIBSSH=ON -DENABLE_UNICODE=OFF -DENABLE_UNIX_SOCKETS=OFF -DCURL_GCC_ANALYZER=ON -DCMAKE_C_FLAGS=-DCURL_DEBUG_NO_WIN32_WND' install: perl mingw-w64-x86_64-libssh type: 'Debug' From 289f6a1844cf24139c40256a0ed28585f16a9636 Mon Sep 17 00:00:00 2001 From: Viktor Szakats Date: Sun, 4 Oct 2026 23:13:16 +0200 Subject: [PATCH 378/481] GHA: update dependency libressl/portable to v4.3.3 Refs: https://ftp.openbsd.org/pub/OpenBSD/LibreSSL/libressl-4.3.3-relnotes.txt https://github.com/libressl/portable/releases/tag/v4.3.3 Closes #23255 Closes #23256 --- .github/workflows/http3-linux.yml | 2 +- .github/workflows/linux.yml | 2 +- .github/workflows/macos.yml | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/http3-linux.yml b/.github/workflows/http3-linux.yml index 2c08f727201b..ddb69e9820d9 100644 --- a/.github/workflows/http3-linux.yml +++ b/.github/workflows/http3-linux.yml @@ -48,7 +48,7 @@ env: # renovate: datasource=github-tags depName=gnutls/gnutls versioning=semver registryUrl=https://github.com GNUTLS_VERSION: 3.8.13 # renovate: datasource=github-tags depName=libressl/portable versioning=semver registryUrl=https://github.com - LIBRESSL_VERSION: 4.3.2 + LIBRESSL_VERSION: 4.3.3 # renovate: datasource=github-releases depName=openssl/openssl versioning=semver extractVersion=^openssl-(?.+)$ registryUrl=https://github.com OPENSSL_VERSION: 4.0.3 # manually bumped diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 324563619350..934c4a9b8f42 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -44,7 +44,7 @@ env: # renovate: datasource=github-releases depName=pizlonator/fil-c versioning=semver-coerced registryUrl=https://github.com FIL_C_VERSION: 0.685 # renovate: datasource=github-tags depName=libressl/portable versioning=semver registryUrl=https://github.com - LIBRESSL_VERSION: 4.3.2 + LIBRESSL_VERSION: 4.3.3 # renovate: datasource=github-tags depName=Mbed-TLS/mbedtls versioning=semver registryUrl=https://github.com MBEDTLS_VERSION: 4.2.0 # manually bumped diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index eb68da83fffb..4f9b63f05adf 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -66,7 +66,7 @@ jobs: MATRIX_BUILD: ${{ matrix.build.generate && 'cmake' || 'autotools' }} MATRIX_OPTIONS: ${{ matrix.build.options }} # renovate: datasource=github-tags depName=libressl/portable versioning=semver registryUrl=https://github.com - LIBRESSL_VERSION: 4.3.1 + LIBRESSL_VERSION: 4.3.3 strategy: fail-fast: false matrix: @@ -120,8 +120,8 @@ jobs: - name: 'build libressl' if: ${{ contains(matrix.build.install_steps, 'libressl') && !steps.cache-libressl.outputs.cache-hit }} run: | - curl --disable --fail --silent --show-error --connect-timeout 15 --max-time 60 --retry 3 --retry-connrefused \ - --location --proto-redir =https "https://github.com/libressl/portable/releases/download/v${LIBRESSL_VERSION}/libressl-${LIBRESSL_VERSION}.tar.gz" --output pkg.bin + curl --disable --fail --silent --show-error --connect-timeout 15 --max-time 120 --retry 6 --retry-connrefused \ + "https://ftp.openbsd.org/pub/OpenBSD/LibreSSL/libressl-${LIBRESSL_VERSION}.tar.gz" --output pkg.bin sha256sum pkg.bin && tar -xzf pkg.bin && rm -f pkg.bin cd "libressl-${LIBRESSL_VERSION}" cmake -B . -G Ninja -DLIBRESSL_APPS=OFF -DLIBRESSL_TESTS=OFF -DCMAKE_INSTALL_PREFIX=/Users/runner/libressl \ From fd33638fe2cf35f08204aad4e10f0cb2d4a2ce50 Mon Sep 17 00:00:00 2001 From: Stefan Eissing Date: Wed, 30 Sep 2026 13:14:31 +0200 Subject: [PATCH 379/481] lib: small performance tweaks - remove `struct curltime now` from multihandle - replace multi_now() calls with curlx_pnow() - api: do not check for recursive use of a callback, it never happens and has effect only in DEBUG builds - add macro `CURL_CONN_IN_SHUTDOWN()` for faster connection checks - Curl_pollfds, faster cleanup - Curl_pollset, faster reset - add `CURL_THRDQ_TRACE()`, avoiding function calls Closes #23186 --- lib/api.c | 15 +++------------ lib/cfilters.c | 6 +++--- lib/connect.c | 2 +- lib/cshutdn.c | 9 ++------- lib/cshutdn.h | 3 +-- lib/multi.c | 36 +++++++++++++++++++----------------- lib/multihandle.h | 2 -- lib/select.c | 13 +++++-------- lib/thrdqueue.h | 7 +++++++ lib/transfer.c | 2 +- lib/vdns/asyn-thrdd.c | 12 ++++-------- 11 files changed, 46 insertions(+), 61 deletions(-) diff --git a/lib/api.c b/lib/api.c index e46a6b738e3f..4ae457365b49 100644 --- a/lib/api.c +++ b/lib/api.c @@ -431,21 +431,12 @@ void Curl_cbapi_enter(struct Curl_mapi_guard *guard, return; } DEBUGASSERT(cbapi_fn_props[fn - CURL_CBAPI_FN_START].fn == fn); - if(multi->callstack.count) { - size_t i; - for(i = multi->callstack.count; i; --i) { - if(multi->callstack.calls[i - 1] == fn) { - /* recursive invocation of the same callback */ - DEBUGASSERT(0); - return; - } - } - } - /* all fine, add to data's callstack */ /* if multi callstack already at max depth, leave */ - if(multi->callstack.count >= CURL_MAPI_MAX_RECURSION) + if(multi->callstack.count >= CURL_MAPI_MAX_RECURSION) { + DEBUGASSERT(0); return; + } multi->callstack.calls[multi->callstack.count] = (uint8_t)fn; ++multi->callstack.count; guard->depth = multi->callstack.count; diff --git a/lib/cfilters.c b/lib/cfilters.c index 39a6db10a03a..5e9cae343463 100644 --- a/lib/cfilters.c +++ b/lib/cfilters.c @@ -699,7 +699,7 @@ CURLcode Curl_conn_adjust_pollset(struct Curl_easy *data, struct easy_pollset *ps) { CURLcode result = CURLE_OK; - bool want_io = !!ps->n; + const bool want_io = !!ps->n; int8_t i; DEBUGASSERT(data); @@ -719,8 +719,8 @@ CURLcode Curl_conn_adjust_pollset(struct Curl_easy *data, * of its own. */ for(i = 0; (i < (int)CURL_ARRAYSIZE(conn->cfilter)) && !result; ++i) { if(conn->cfilter[i] && - (want_io || !Curl_conn_is_connected(conn, i) || - Curl_cshutdn_has_started(conn, i))) + (want_io || !conn->cfilter[i]->connected || + CURL_CONN_IN_SHUTDOWN(conn, i))) result = Curl_conn_cf_adjust_pollset(conn->cfilter[i], data, ps); } return result; diff --git a/lib/connect.c b/lib/connect.c index e20df00d5d8b..82bca3ece550 100644 --- a/lib/connect.c +++ b/lib/connect.c @@ -73,7 +73,7 @@ UNITTEST timediff_t timeleft_now_ms(struct Curl_easy *data, timediff_t timeleft_ms = 0; timediff_t ctimeleft_ms = 0; - if(data->conn && Curl_cshutdn_has_started(data->conn, FIRSTSOCKET)) + if(data->conn && CURL_CONN_IN_SHUTDOWN(data->conn, FIRSTSOCKET)) return Curl_cshutdn_timeleft_ms(data, data->conn, FIRSTSOCKET); else if(Curl_is_connecting(data)) { timediff_t ctimeout_ms = (data->set.connecttimeout > 0) ? diff --git a/lib/cshutdn.c b/lib/cshutdn.c index 7c4628014d2e..00b5d0beac4c 100644 --- a/lib/cshutdn.c +++ b/lib/cshutdn.c @@ -96,11 +96,6 @@ void Curl_cshutdn_clear_timer(struct Curl_easy *data, int8_t sockindex) data->conn->shutdown.start_ms[sockindex] = -1; } -bool Curl_cshutdn_has_started(struct connectdata *conn, int8_t sockindex) -{ - return conn->shutdown.start_ms[sockindex] >= 0; -} - static void cshutdn_run_conn_handler(struct Curl_easy *data, struct connectdata *conn) { @@ -152,7 +147,7 @@ CURLcode Curl_cshutdn_try_once_idx(struct Curl_easy *data, } *done = FALSE; - if(!Curl_cshutdn_has_started(data->conn, sockindex)) { + if(!CURL_CONN_IN_SHUTDOWN(data->conn, sockindex)) { Curl_cshutdn_start_timer(data, sockindex, 0); } else { @@ -195,7 +190,7 @@ static void cshutdn_run_once(struct Curl_easy *data, /* We expect to be attached when called */ DEBUGASSERT(data->conn == conn); - if(!Curl_cshutdn_has_started(conn, FIRSTSOCKET)) { + if(!CURL_CONN_IN_SHUTDOWN(conn, FIRSTSOCKET)) { Curl_cshutdn_start_timer(data, FIRSTSOCKET, 0); } diff --git a/lib/cshutdn.h b/lib/cshutdn.h index efdcf1f55f63..f5ba3509958c 100644 --- a/lib/cshutdn.h +++ b/lib/cshutdn.h @@ -54,8 +54,7 @@ timediff_t Curl_cshutdn_timeleft_ms(struct Curl_easy *data, struct connectdata *conn, int8_t sockindex); -/* TRUE iff shutdown at sockindex has been started */ -bool Curl_cshutdn_has_started(struct connectdata *conn, int8_t sockindex); +#define CURL_CONN_IN_SHUTDOWN(c,i) ((c)->shutdown.start_ms[(i)] >= 0) /* Shutdown the connection at `sockindex` non-blocking. * Will start the shutdown timer if not already set. diff --git a/lib/multi.c b/lib/multi.c index ec8fcec21027..8f8d0b525fba 100644 --- a/lib/multi.c +++ b/lib/multi.c @@ -87,12 +87,6 @@ static void multi_xfer_tbl_dump(struct Curl_multi *multi); /* Get the # of transfers current in process/pending. */ static uint32_t multi_xfers_running(struct Curl_multi *multi); -static const struct curltime *multi_now(struct Curl_multi *multi) -{ - curlx_pnow(&multi->now); - return &multi->now; -} - /* function pointer called once when entering a state */ typedef void (*mstate_enter_func)(struct Curl_easy *data, CURLMstate from_state); @@ -230,6 +224,7 @@ struct Curl_multi *Curl_multi_handle(uint32_t xfer_table_size, size_t dnssize, /* dns hash */ size_t sesssize) /* TLS session cache */ { + struct curltime created; struct Curl_multi *multi = curlx_calloc(1, sizeof(struct Curl_multi)); if(!multi) @@ -250,8 +245,8 @@ struct Curl_multi *Curl_multi_handle(uint32_t xfer_table_size, multi->admin->state.internal = TRUE; /* Now we can use curlx_* things safely */ - curlx_pnow(&multi->now); - Curl_timeouts_init(&multi->timeouts, &multi->now); + curlx_pnow(&created); + Curl_timeouts_init(&multi->timeouts, &created); multi_timeouts_init(multi->admin); Curl_dnscache_init(&multi->dnscache, dnssize); @@ -2896,10 +2891,11 @@ static CURLMcode multi_perform(struct Curl_multi *multi, int *running_handles) { CURLMcode returncode = CURLM_OK; - struct curltime start = *multi_now(multi); + struct curltime start; uint32_t mid; struct Curl_sigpipe_ctx sigpipe_ctx; + curlx_pnow(&start); sigpipe_init(&sigpipe_ctx); if(Curl_uint32_bset_first(&multi->process, &mid)) { @@ -2948,7 +2944,7 @@ static CURLMcode multi_perform(struct Curl_multi *multi, if(data->mstate == MSTATE_PENDING) { bool stream_unused; CURLcode result_unused; - if(multi_handle_timeout(data, multi_now(multi), + if(multi_handle_timeout(data, Curl_pgrs_now(data), &stream_unused, &result_unused)) { infof(data, "PENDING handle timeout"); move_pending_to_connect(multi, data); @@ -3250,6 +3246,7 @@ static CURLMcode multi_socket(struct Curl_multi *multi, { CURLMcode mresult = CURLM_OK; struct Curl_sigpipe_ctx pipe_ctx; + struct curltime now; uint32_t run_xfers; (void)ev_bitmask; @@ -3287,7 +3284,8 @@ static CURLMcode multi_socket(struct Curl_multi *multi, goto out; } - multi_mark_expired_as_dirty(multi, multi_now(multi)); + curlx_pnow(&now); + multi_mark_expired_as_dirty(multi, &now); mresult = multi_run_dirty(multi, &pipe_ctx, &run_xfers); if(mresult) goto out; @@ -3298,7 +3296,8 @@ static CURLMcode multi_socket(struct Curl_multi *multi, * to set a 0 timeout and call us again, we run them here. * Do that only once or it might be unfair to transfers on other * sockets. */ - multi_mark_expired_as_dirty(multi, &multi->now); + curlx_pnow(&now); + multi_mark_expired_as_dirty(multi, &now); mresult = multi_run_dirty(multi, &pipe_ctx, &run_xfers); } @@ -3526,17 +3525,20 @@ static void multi_timeout(struct Curl_multi *multi, } if(multi_has_dirties(multi)) { - if(pexire_offset_us) - *pexire_offset_us = Curl_timeouts_offset_us(&multi->timeouts, - multi_now(multi)); + if(pexire_offset_us) { + struct curltime now; + curlx_pnow(&now); + *pexire_offset_us = Curl_timeouts_offset_us(&multi->timeouts, &now); + } *timeout_ms = 0; return; } else { - const struct curltime *pnow = multi_now(multi); + struct curltime now; uint32_t mid; - *timeout_ms = Curl_timeouts_next_ms(&multi->timeouts, pnow, + curlx_pnow(&now); + *timeout_ms = Curl_timeouts_next_ms(&multi->timeouts, &now, pexire_offset_us, &mid); #ifdef CURLVERBOSE if(mid != UINT32_MAX) { diff --git a/lib/multihandle.h b/lib/multihandle.h index 0073496a7503..6a0bf5118cc6 100644 --- a/lib/multihandle.h +++ b/lib/multihandle.h @@ -106,8 +106,6 @@ struct Curl_multi { struct Curl_mapi_stack callstack; /* multi api calls ongoing */ - /* current time for transfers running in this multi handle */ - struct curltime now; /* expiration times for all attached easy handles */ struct Curl_timeouts timeouts; diff --git a/lib/select.c b/lib/select.c index a161d93d3205..3e45fda78018 100644 --- a/lib/select.c +++ b/lib/select.c @@ -348,9 +348,11 @@ void Curl_pollfds_cleanup(struct curl_pollfds *cpfds) { DEBUGASSERT(cpfds); if(cpfds->allocated_pfds) { - curlx_free(cpfds->pfds); + curlx_safefree(cpfds->pfds); + cpfds->count = 0; + cpfds->allocated_pfds = FALSE; } - memset(cpfds, 0, sizeof(*cpfds)); + cpfds->n = 0; } static CURLcode cpfds_increase(struct curl_pollfds *cpfds, unsigned int inc) @@ -476,15 +478,10 @@ unsigned int Curl_waitfds_add_ps(struct Curl_waitfds *cwfds, void Curl_pollset_reset(struct easy_pollset *ps) { - unsigned int i; - ps->n = 0; #ifdef DEBUGBUILD DEBUGASSERT(ps->init == CURL_EASY_POLLSET_MAGIC); #endif - DEBUGASSERT(ps->count); - for(i = 0; i < ps->count; i++) - ps->sockets[i] = CURL_SOCKET_BAD; - memset(ps->actions, 0, ps->count * sizeof(ps->actions[0])); + ps->n = 0; } void Curl_pollset_init(struct easy_pollset *ps) diff --git a/lib/thrdqueue.h b/lib/thrdqueue.h index 0d84f6f6f144..ffa93d36b811 100644 --- a/lib/thrdqueue.h +++ b/lib/thrdqueue.h @@ -120,6 +120,13 @@ CURLcode Curl_thrdq_set_props(struct curl_thrdq *tqueue, #ifdef CURLVERBOSE void Curl_thrdq_trace(struct curl_thrdq *tqueue, struct Curl_easy *data); +#define CURL_THRDQ_TRACE(t,d) \ + do { \ + if(Curl_trc_ft_is_verbose((d), &Curl_trc_feat_threads)) \ + Curl_thrdq_trace((t), (d)); \ + } while(0) +#else +#define CURL_THRDQ_TRACE(t,d) Curl_nop_stmt #endif #endif /* USE_THREADS */ diff --git a/lib/transfer.c b/lib/transfer.c index 3fc118117e3d..3f0f2157ae7b 100644 --- a/lib/transfer.c +++ b/lib/transfer.c @@ -154,7 +154,7 @@ static bool xfer_recv_shutdown_started(struct Curl_easy *data) { if(!data || !data->conn) return FALSE; - return Curl_cshutdn_has_started(data->conn, data->conn->recv_idx); + return CURL_CONN_IN_SHUTDOWN(data->conn, data->conn->recv_idx); } CURLcode Curl_xfer_send_shutdown(struct Curl_easy *data, bool *done) diff --git a/lib/vdns/asyn-thrdd.c b/lib/vdns/asyn-thrdd.c index b344f83026e8..e5086c4ebb24 100644 --- a/lib/vdns/asyn-thrdd.c +++ b/lib/vdns/asyn-thrdd.c @@ -513,7 +513,7 @@ void Curl_async_thrdd_multi_destroy(struct Curl_multi *multi, bool join) if(multi->resolv_thrdq) { #ifdef CURLVERBOSE CURL_TRC_DNS(multi->admin, "destroy thread queue+pool, join=%d", join); - Curl_thrdq_trace(multi->resolv_thrdq, multi->admin); + CURL_THRDQ_TRACE(multi->resolv_thrdq, multi->admin); #endif Curl_thrdq_destroy(multi->resolv_thrdq, join); multi->resolv_thrdq = NULL; @@ -596,7 +596,7 @@ void Curl_async_thrdd_multi_process(struct Curl_multi *multi) } async_thrdd_item_free(item); } - VERBOSE(Curl_thrdq_trace(multi->resolv_thrdq, multi->admin)); + CURL_THRDQ_TRACE(multi->resolv_thrdq, multi->admin); } CURLcode Curl_async_thrdd_multi_set_props(struct Curl_multi *multi, @@ -683,9 +683,7 @@ CURLcode Curl_async_getaddrinfo(struct Curl_easy *data, goto out; } -#ifdef CURLVERBOSE - Curl_thrdq_trace(data->multi->resolv_thrdq, data); -#endif + CURL_THRDQ_TRACE(data->multi->resolv_thrdq, data); out: if(!async->queries_ongoing) @@ -928,9 +926,7 @@ CURLcode Curl_async_take_result(struct Curl_easy *data, *pdns = dns; dns = NULL; } -#ifdef CURLVERBOSE - Curl_thrdq_trace(data->multi->resolv_thrdq, data); -#endif + CURL_THRDQ_TRACE(data->multi->resolv_thrdq, data); out: Curl_dns_entry_unlink(data, &dns); From d56732fdafd4463e88cd83b53087bedc6192f145 Mon Sep 17 00:00:00 2001 From: xhon-pelushi Date: Thu, 17 Sep 2026 07:01:12 -0400 Subject: [PATCH 380/481] KNOWN_BUGS: widen the aws-sigv4 payload hash entry The entry names multipart/form-data only, so someone hitting the same bug on a -T upload will not find it. The defect is not specific to mime: calc_payload_hash() (lib/http_aws_sigv4.c:574) hashes only data->set.postfields, which is NULL for both HTTPREQ_POST_MIME and a HTTPREQ_PUT fed from a file. post_data_len then stays 0 and Curl_sha256it() is called with (NULL, 0), so the signature is computed over SHA256(""). Closes #23105 --- docs/KNOWN_BUGS.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/docs/KNOWN_BUGS.md b/docs/KNOWN_BUGS.md index 24b38acc74fc..7229063206ac 100644 --- a/docs/KNOWN_BUGS.md +++ b/docs/KNOWN_BUGS.md @@ -190,7 +190,15 @@ https://curl.se/mail/lib-2012-07/0073.html # Authentication -## `--aws-sigv4` does not handle multipart/form-data correctly +## `--aws-sigv4` signs an empty payload hash for bodies not held in memory + +`--aws-sigv4` hashes only the in-memory POST fields when it computes the payload hash, so a request +whose body comes from anywhere else is signed as if it had no body at all: the signature is +computed over `SHA256("")` while a different body goes on the wire. This affects `-F` +(multipart/form-data) and `-T` (PUT upload) alike. + +`aws:amz::s3` is not affected, because it sends and signs `UNSIGNED-PAYLOAD` instead. Every +other service gets the empty hash, with no `x-amz-content-sha256` header sent, and no warning. [curl issue 13351](https://github.com/curl/curl/issues/13351) From 84b99b1834bb450383016e86df50fe0aadea422b Mon Sep 17 00:00:00 2001 From: Viktor Szakats Date: Sun, 4 Oct 2026 13:48:27 +0200 Subject: [PATCH 381/481] GHA: drop unused bot configs (stale-bot and lock-threads) These bots have been disabled a good while ago, leaving their configs unused. Follow-up to 3ed3db5c0488ec5a5ff3ca463a6625d0328f641e Follow-up to ced57e9a9536ee2fe06c700c5274298f3ceaf33d #1398 Closes #23250 --- .github/lock.yml | 12 ------------ .github/stale.yml | 21 --------------------- 2 files changed, 33 deletions(-) delete mode 100644 .github/lock.yml delete mode 100644 .github/stale.yml diff --git a/.github/lock.yml b/.github/lock.yml deleted file mode 100644 index ab30d2a82701..000000000000 --- a/.github/lock.yml +++ /dev/null @@ -1,12 +0,0 @@ -# Copyright (C) Daniel Stenberg, , et al. -# -# SPDX-License-Identifier: curl - -# Configuration for lock-threads - https://github.com/dessant/lock-threads - -# Number of days of inactivity before a closed issue or pull request is locked -daysUntilLock: 90 -# Comment to post before locking. Set to `false` to disable -lockComment: false -# Limit to only `issues` or `pulls` -# only: issues diff --git a/.github/stale.yml b/.github/stale.yml deleted file mode 100644 index 1c2b57c2cf90..000000000000 --- a/.github/stale.yml +++ /dev/null @@ -1,21 +0,0 @@ -# Copyright (C) Daniel Stenberg, , et al. -# -# SPDX-License-Identifier: curl - -# Number of days of inactivity before an issue becomes stale -daysUntilStale: 180 -# Number of days of inactivity before a stale issue is closed -daysUntilClose: 14 -# Issues with these labels are never considered stale -exemptLabels: - - pinned - - security -# Label to use when marking an issue as stale -staleLabel: stale -# Comment to post when marking an issue as stale. Set to `false` to disable -markComment: > - This issue has been automatically marked as stale because it has not had - recent activity. It is closed if no further activity occurs. Thank you - for your contributions. -# Comment to post when closing a stale issue. Set to `false` to disable -closeComment: false From fbf3d86b1f29e3678f0955e4aab92ff6e5e5afcc Mon Sep 17 00:00:00 2001 From: Viktor Szakats Date: Sun, 4 Oct 2026 15:12:38 +0200 Subject: [PATCH 382/481] tests: rename debug env to `CURL_DEBUG_VERSION` From `CURL_VERSION`. To avoid confusion with the non-debug `CURL_VERSION` macro, and to avoid possible, difficult-to-track-down fallouts when `CURL_VERSION` is legitimately used for something else. Refs: https://github.com/libssh2/libssh2/pull/2590/commits/51333f9d7c9646b725402498ee2ce5e62582ba8d https://github.com/libssh2/libssh2/pull/2590 https://github.com/libssh2/libssh2/commit/bb8e0957b8091e5b6668b2ffe647721f5cc2fb7d Closes #23252 --- lib/version.c | 5 +++-- tests/data/test970 | 2 +- tests/data/test972 | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/version.c b/lib/version.c index 32d4498d0735..ec796e87baac 100644 --- a/lib/version.c +++ b/lib/version.c @@ -186,8 +186,9 @@ char *curl_version(void) int j; #ifdef DEBUGBUILD - /* Override version string when environment variable CURL_VERSION is set */ - const char *debugversion = getenv("CURL_VERSION"); + /* Override version string when environment variable CURL_DEBUG_VERSION is + set */ + const char *debugversion = getenv("CURL_DEBUG_VERSION"); if(debugversion) { curl_msnprintf(out, sizeof(out), "%s", debugversion); return out; diff --git a/tests/data/test970 b/tests/data/test970 index e936865d2de4..c579d96435d6 100644 --- a/tests/data/test970 +++ b/tests/data/test970 @@ -37,7 +37,7 @@ proxy CURL_TIME=13 CURL_DEBUG_SIZE=4019 -CURL_VERSION=curl-unit-test-fake-version +CURL_DEBUG_VERSION=curl-unit-test-fake-version HTTP GET with JSON output diff --git a/tests/data/test972 b/tests/data/test972 index 725da28019b5..731299f995bf 100644 --- a/tests/data/test972 +++ b/tests/data/test972 @@ -38,7 +38,7 @@ proxy CURL_TIME=13 CURL_DEBUG_SIZE=4019 -CURL_VERSION=curl-unit-test-fake-version +CURL_DEBUG_VERSION=curl-unit-test-fake-version Verify JSON output From e351db113dc031d55700dea0da87c90380e9643d Mon Sep 17 00:00:00 2001 From: Viktor Szakats Date: Sun, 4 Oct 2026 16:04:59 +0200 Subject: [PATCH 383/481] sshserver.pl: set `PerSourcePenalties no` when sshd supports it To avoid the chance of the test server rejecting the local IP range after certain failures. Which behavior is enabled by default in sshd 9.8+. Refs: https://man.openbsd.org/sshd_config#PerSourcePenalties https://www.openssh.org/txt/release-9.8 Closes #23253 --- tests/sshserver.pl | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/sshserver.pl b/tests/sshserver.pl index 1aacbb53b766..0659ccb677a5 100755 --- a/tests/sshserver.pl +++ b/tests/sshserver.pl @@ -549,6 +549,7 @@ sub logmsg { # PermitRootLogin : OpenSSH 1.2.1 and later # PermitTunnel : OpenSSH 4.3.0 and later # PermitUserEnvironment : OpenSSH 3.5.0 and later +# PerSourcePenalties : OpenSSH 9.8.0 and later # PidFile : OpenSSH 2.1.0 and later # Port : OpenSSH 1.2.1 and later # PrintLastLog : OpenSSH 2.9.0 and later @@ -641,6 +642,9 @@ sub logmsg { push @cfgarr, 'PasswordAuthentication no'; push @cfgarr, 'PermitEmptyPasswords no'; push @cfgarr, 'PermitRootLogin no'; +if(($sshdid =~ /OpenSSH/) && ($sshdvernum >= 980)) { + push @cfgarr, 'PerSourcePenalties no'; +} push @cfgarr, 'PrintLastLog no'; push @cfgarr, 'PrintMotd no'; push @cfgarr, 'PubkeyAuthentication yes'; From 6d013631195b1c349e5f783ac8b58ad376c0bdde Mon Sep 17 00:00:00 2001 From: Daniel Stenberg Date: Mon, 5 Oct 2026 00:10:39 +0200 Subject: [PATCH 384/481] RELEASE-NOTES: synced --- RELEASE-NOTES | 131 +++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 119 insertions(+), 12 deletions(-) diff --git a/RELEASE-NOTES b/RELEASE-NOTES index b45911c95883..a127afd684ff 100644 --- a/RELEASE-NOTES +++ b/RELEASE-NOTES @@ -4,8 +4,8 @@ curl and libcurl 8.23.0 Command line options: 278 curl_easy_setopt() options: 312 Public functions in libcurl: 100 - Authors: 1536 - Contributors: 3823 + Authors: 1538 + Contributors: 3831 This release includes the following changes: @@ -16,6 +16,10 @@ This release includes the following changes: This release includes the following bugfixes: + o apple-sectrust: check ocsp copy alloc [292] + o apple: shorten versions in `__builtin_available()` calls [251] + o apple: sync hash init calls, add comments [217] + o apple: sync up version guard macro style [263] o base64: avoid repeated input loads when encoding [114] o base64: optimize decode lookup table and quantum loop [88] o build: convert HAS_TIME_T_UNSIGNED to a compile-time check [65] @@ -23,35 +27,49 @@ This release includes the following bugfixes: o capsule: reject a capsule that can never be buffered [84] o cf-h1-proxy: reset chunk state on new request [146] o cf-h1-proxy: stricter status line parsing [145] + o cf-ngtcp2-cmn: fix possible `-Wdeclaration-after-statement` [296] o cf-ngtcp2-proxy: check for stream close on rx win update [159] o cf-ngtcp2-proxy: fix rx flow control [174] + o cf-socket: adjust NO_SYN_RETRANSMISSIONS for older Windows [275] o cf-socket: clamp keepalive seconds-to-milliseconds conversions [167] o clang-tidy: merge nested `if()`s [39] + o cmake/Find*: drop deprecated compatibility config variables [259] + o cmake: enable C++ library for GCC in AWS-LC/BoringSSL static builds [232] + o cmake: link llvm stdc++ lib to AWS-LC/BoringSSL-specific feature detections only [271] o config2setopts: always set the security options for SCP/SFTP [67] o conncache: avoid evicting connect-only connections [79] + o conncache: never discard a connection still in use [208] o conncache: remove bundle dest [50] o connectdata: use less curltime [34] o cookie: accept slightly longer lines in cookie jars [105] o cookie: ignore individual cookie errors when loading [6] + o cpool: do not use admin handle for conn lookup [299] o cshutdown: make it smaller [60] + o curl-openssl.m4: sync LibreSSL detection method with rest of forks [218] o curl_addrinfo: drop redundant parentheses [41] o curl_easy_send/recv: check pointer arg [175] o curl_fopen: restore the uid and gid checks [70] o curl_formadd.md: mention the strlen() for names as well [29] + o curl_setup.h: drop compatibility raw C flag `ENABLE_IPV6` [260] o CURLINFO_EFFECTIVE_URL.md: mention a short lifetime [116] o CURLOPT*ISSUERCERT*: not verified cryptographically [28] o CURLOPT_POSTFIELDSIZE.md: improve wording [144] o CURLOPT_SHARE.md: remove bad explanation for cookie sharing [7] o CURLOPT_SSL_CTX_FUNCTION.md: client cert caveats [87] + o curlx_fopen: add macro to cast file mode [245] o cw-out: avoid O(n^2) work while buffering paused output [74] o cw-out: recheck callbacks for each call [134] o cw-pause: fix O(N^2) list traversal in cw_pause_flush [137] + o DEPENDENCIES.md: fix release date for valgrind 3.0.0 to 2005-08-03 + o DEPRECATE.md: reflect SMB removal [252] o digest: check peer equality for staleness [24] o digest: fix maximum length quoted value parsing [22] o digest: parse without storing 'value' in local buffer [20] o digest: quote the digest-uri param as well [85] o dnscache/conncache tweaks [75] + o dnscache: check hostname on lookup [209] o docs: add the missing .html to the url-encode book link [3] + o docs: document and align the blocking situations [163] o doh: reject undersized HTTPS RDATA [11] o dynhds: grow header array geometrically [73] o easy: duplicating a zero byte memory area needs no malloc [91] @@ -67,6 +85,7 @@ This release includes the following bugfixes: o ftp: URL encode file names when wildcarding [61] o ftplistparser: no more overwriting set.write_func [100] o ftplistparser: when wildcarding, skip files with slashes [125] + o ftpserver.pl: fix warnings when running test 804 [257] o getinfo: make sure CURLINFO_EFFECTIVE_URL does not contain creds [107] o getinfo: make sure CURLINFO_REDIRECT_URL does not contain creds [94] o getparam: switch off CONNECT headers when using -J [4] @@ -82,31 +101,42 @@ This release includes the following bugfixes: o HTTP-CONNECT: do not react to 401 responses [31] o http2: changes in error handling [53] o http2: don't send a CURLE_SEND_ERROR after a valid http2 response [71] + o http2: fix reset after 1xx response [289] + o http2: init some callbacks for HTTP/2 push easy handles [191] o http2: initialize URL handle for server pushes [104] + o http2: limit trailers and headers count [211] o http2: update a refused stream's error after GOAWAY [102] o http: abide to close-rules when transfer-encoding off [132] o http: ignore Proxy-Authenticate unless the response comes from a proxy [120] o http: only enable Negotiate/NTLM to allowed origins [99] o http: reject resumed uploads [16] + o http_aws_sigv4: fix the s3express service name [273] + o http_chunks: limit trailers size [212] o httpsig-key.md: correct the generating Ed25519 keys commands [97] o httpsrr+ech: check target and port [133] o if2ip: check interface name case sensitive [158] o imap: compare selected mailbox names case-sensitively [57] + o imap: do size checks using 64-bit math [291] o inet_pton: reject a colon that ends an IPv6 address [179] + o IPFS.md: replace sunset link, allowlist another one to avoid `mdlinkcheck` hang [168] + o KNOWN_BUGS: drop the curl-config private details entry [290] o KNOWN_BUGS: drop the empty error buffer entry, it no longer happens [66] o ldap: base64-encode a non-SAFE DN and reject a control-byte type name [55] o lib: connection shutdown, cleanup code [92] o lib: make private functions static and remove unused code [83] o lib: reject internal handles in the multi/easy public API [103] + o lib: replace `CURLX_FILE_MODE()` with `mode_t` cast [205] o libssh2: match hashed known_hosts entries by host [30] o libssh2: skip repeat checkp() for already-absent hashed key types [82] o libssh: work with empty priv_key [143] o mbedtls: fix ticket handling [52] o mbedtls: resend length check [140] + o mdlinkcheck: pass `--retry-max-time` to avoid server-controlled long retry times [161] o mime: bound Curl_mime_duppart() recursion depth [164] o mime: bound Curl_mime_prepare_headers() recursion depth [76] o mime: do curl_mime_free() non-recursively [48] o mprintf: avoid scanning strings twice for unbounded %s [110] + o mprintf: make mprintf() and mfprintf() return -1 on error [279] o mprintf: stage output and emit it in runs [38] o mprintf: use size_t width for the bare Windows %I modifier [178] o mqtt: drain queued output before advancing the state machine [118] @@ -117,6 +147,7 @@ This release includes the following bugfixes: o multi: remove redundant completion message list [32] o multi: store timeout epoch as whole seconds [36] o multi_ntfy: fix handling of more than 128 notifications in a batch [86] + o multi_ntfy: recheck callback on dispatch [228] o multi_ntfy: replace enabled bitset with inline flags [44] o multihandle: some easy shrinks [33] o netrc: empty quoted values now produce a valid empty string [62] @@ -124,23 +155,35 @@ This release includes the following bugfixes: o ngtcp2: shrink max sendbuf [13] o openldap: hand it a dup'd socket, not curl's own o openssl/gtls: reject CRLfile with native CA store, matching rustls [27] + o openssl: fix OpenSSL v4.1 no-deprecated build [190] o openssl: use non-deprecated API for MD5/MD4 [69] o os400: document curl_formadd_CCSID multi-chunk peculiarity [5] o os400: restore HAVE_FCHMOD [181] + o os400: use correct free function [189] o ossl_verifyhost: remove assumption of null-termination of ASN1_STRING. [26] o output-dir.md: expand on its behavior [151] o parsedate: find day names, months and time zones faster [68] o parsedate: remove the unused leading three letters [106] + o parsedate: validate NULL date pointer in curl_getdate [213] + o peer: compare zoneid case sensitive [229] o peer: parsing a zoneid as number fix [139] o peer: preserve ipv6 props from "connect-to" [155] o perf-tests: output two-digital decimals always [96] o pingpong: add a max loop counter for Curl_pp_readresp [113] o pytest: check server status without ssl verify [154] + o pytest: silence pytest warnings [261] o rtsp: accept a Content-Length body on methods that expected none [115] o rtsp: do not send a learned session id to another origin [10] + o runtests: add extra room for mininum number of tests in sliced jobs [201] o runtests: Added runnernums in order to have O(1) lookup [45] + o rustls: break recv loop on empty plain text [298] o schannel: auto_client_cert conn matching [17] o schannel: clear borrowed sslContext on close [15] + o schannel: fix `-Wextra-semi-stmt` with clang and extra debug enabled [267] + o schannel: fix compiler warning `-Wstrict-aliasing` in ALPN code [243] + o sectrust: add guards for rest of Apple OS flavors [250] + o socketpair: fix accept loop [210] + o socketpair: use `pipe2()` on Apple OSes (requires SDK 27) [262] o socks: support CURLAUTH_ONLY in CURLOPT_SOCKS5_AUTH [124] o socks: track SOCKS5 authentication per socket, not per connection [171] o socksd: count connections per test, widen the method2 sentinel @@ -150,9 +193,16 @@ This release includes the following bugfixes: o sws: fix connection-monitor for connections closed before a request [153] o telnet: make it work through proxy tunnels [112] o test 1605: move from unit to libtest [49] + o test/servers.pm: restrict h2/3 port reuse [203] o test1399: improve reliability [9] o test1679: unset `SSL_CERT_FILE`, use `TESTNUMBER` [58] + o test2007: bump to use SHA256 public key hash (was: MD5) [226] + o test2007: set `MSYS2_ARG_CONV_EXCL` to avoid flaky failures [216] + o test2072: enable on Cygwin/MSYS2 [256] o test: restore the old test 1609 as 3271 [162] + o tests/http: use @classmethod when appropriate [231] + o tests: fix typos in a comment [247] + o tests: require valgrind 3+ (2007-08-03) [282] o tftp: verify direction in a few places [101] o tool: (re)set SSL verification in --libcurl output [1] o tool: buffer headers before remote filename selection [98] @@ -162,7 +212,6 @@ This release includes the following bugfixes: o tool_help: guard category[2] access when category is bare "-" [59] o tool_ipfs: keep the path percent-encoded when rewriting to the gateway [138] o tool_operate: clear the retry flag when the output file fails [122] - o tool_operate: set stdin non-blocking unconditionally [130] o u8_strset, u32_ptrset: fix boundaries handling [131] o uint-bset: skip unused slots when counting o uint-table: add remove-max precaution [165] @@ -174,12 +223,16 @@ This release includes the following bugfixes: o urlapi: run the urlparser perf test faster [54] o urldata: remove conn->given [40] o urldata: remove ssl_primary_config from easy handle [78] + o urldata: shrink connectdata negotiate states [207] o vdns: announce resolver start before the HTTPS RR query is sent [173] o vquic: fix preprocessor check for old Darwin versions [8] + o vquic: use `CURLX_FILE_MODE()` in `Curl_qlogdir()` [238] o vssh: do not busy-loop in blocking state machine without a timeout [121] o vtls: fix Curl_cert_hostcheck when the pattern is not a C string [12] + o vtls_scache: remember lock state locally [297] o warnless: dedupe Intel compiler warning suppression pragmas [42] o wolfssl: remove BIO destroy callback [157] + o ws: check pointer args in curl_ws_recv [192] o ws: do not auto-pong in raw mode [123] o x509: stricter parsing [2] @@ -202,20 +255,22 @@ Planned upcoming removals include: This release would not have looked like this without help, code, reports and advice from friends like these: - 1rhino2 on hackerone, Adam Shirt, Andreas Westin, Artem Prilutskiy, - Aviv Engelberg, Axel Mierczuk, Bobbeh Rhino, bupt-Yy-young on github, - Charles Muehlberger, Christian Hesse, Christian Ullrich, Claude, - Dan Fandrich, Daniel Lang, Daniel Stenberg, David Benjamin, + 1rhino2 on hackerone, Adam Shirt, Alexander Köplinger, Andreas Westin, + Artem Prilutskiy, aschank on github, Aviv Engelberg, Axel Mierczuk, + Bobbeh Rhino, bupt-Yy-young on github, Charles Muehlberger, Christian Hesse, + Christian Ullrich, Claude, Dan Fandrich, Daniel Lang, Daniel McCarney, + Daniel S. Roche, Daniel Stenberg, David Benjamin, dependabot[bot], Fengxiaoxx on github, Fernando Gallardo, Filippo Tedeschi, Florent Castelli, Fredrik Blau, Huang Yiheng, Ilias Aberkane, Jorge Rocamora, Joshua Rogers, - ldm0, Luna Tong, Martin Dukek, Max Dymond, Mihai-Gabriel Marin, minnnjuuu, + JuanCarlosgg on github, KayanoLiam on github, ldm0, Luna Tong, Marcel Raad, + Martin Dukek, Max Dymond, Mihai-Gabriel Marin, minnnjuuu, Mohammed K. Fathy, Muhamad Arga Reksapati, OSS-Fuzz, Paolo Ganci, Patrick Monnerat, rawsun007, Ray Satiro, renovate[bot], Rhino, Richard Payne, Ron Kuper, Roshan Ramani, Rudolf Polzer, Samuel Henrique, Sebastian Raase, Skye Soss, Stanislav Fort, Stefan Eissing, Stephen Psaradellis, szedenik-adam on github, - Thanniru Sai Teja, Tyler Yankee, Un1q32, Viktor Szakats, xhon-pelushi, - Yechan Bae - (56 contributors) + Thanniru Sai Teja, Tip ten Brink, Tyler Yankee, Un1q32, Viktor Szakats, + xhon-pelushi, Yechan Bae, yuwk + (67 contributors) References to bug reports and discussions on issues: @@ -348,7 +403,6 @@ References to bug reports and discussions on issues: [127] = https://curl.se/bug/?i=22997 [128] = https://curl.se/bug/?i=22996 [129] = https://curl.se/bug/?i=22993 - [130] = https://curl.se/bug/?i=23081 [131] = https://curl.se/bug/?i=22991 [132] = https://curl.se/bug/?i=22950 [133] = https://curl.se/bug/?i=23086 @@ -373,11 +427,14 @@ References to bug reports and discussions on issues: [158] = https://curl.se/bug/?i=23056 [159] = https://curl.se/bug/?i=23054 [160] = https://curl.se/bug/?i=23051 + [161] = https://curl.se/bug/?i=23245 [162] = https://curl.se/bug/?i=23052 + [163] = https://curl.se/bug/?i=23006 [164] = https://curl.se/bug/?i=23023 [165] = https://curl.se/bug/?i=23034 [166] = https://curl.se/bug/?i=23043 [167] = https://curl.se/bug/?i=23045 + [168] = https://curl.se/bug/?i=23245 [171] = https://curl.se/bug/?i=23014 [173] = https://curl.se/bug/?i=23037 [174] = https://curl.se/bug/?i=23038 @@ -386,3 +443,53 @@ References to bug reports and discussions on issues: [179] = https://curl.se/bug/?i=23028 [180] = https://curl.se/bug/?i=23027 [181] = https://curl.se/bug/?i=23026 + [189] = https://curl.se/bug/?i=23216 + [190] = https://curl.se/bug/?i=23215 + [191] = https://curl.se/bug/?i=23183 + [192] = https://curl.se/bug/?i=23050 + [201] = https://curl.se/bug/?i=23211 + [203] = https://curl.se/bug/?i=23208 + [205] = https://curl.se/bug/?i=23181 + [207] = https://curl.se/bug/?i=23192 + [208] = https://curl.se/bug/?i=23189 + [209] = https://curl.se/bug/?i=23169 + [210] = https://curl.se/bug/?i=23166 + [211] = https://curl.se/bug/?i=23165 + [212] = https://curl.se/bug/?i=23162 + [213] = https://curl.se/bug/?i=23156 + [216] = https://curl.se/bug/?i=23146 + [217] = https://curl.se/bug/?i=23199 + [218] = https://curl.se/bug/?i=23197 + [226] = https://curl.se/bug/?i=23184 + [228] = https://curl.se/bug/?i=23167 + [229] = https://curl.se/bug/?i=23164 + [231] = https://curl.se/bug/?i=23148 + [232] = https://curl.se/bug/?i=23179 + [238] = https://curl.se/bug/?i=23171 + [243] = https://curl.se/bug/?i=23163 + [245] = https://curl.se/bug/?i=23157 + [247] = https://curl.se/bug/?i=23153 + [250] = https://curl.se/bug/?i=23149 + [251] = https://curl.se/bug/?i=23150 + [252] = https://curl.se/bug/?i=23147 + [256] = https://curl.se/bug/?i=23142 + [257] = https://curl.se/bug/?i=23140 + [259] = https://curl.se/bug/?i=23139 + [260] = https://curl.se/bug/?i=23138 + [261] = https://curl.se/bug/?i=23135 + [262] = https://curl.se/bug/?i=23126 + [263] = https://curl.se/bug/?i=23133 + [267] = https://curl.se/bug/?i=23129 + [271] = https://curl.se/bug/?i=23122 + [273] = https://curl.se/bug/?i=23120 + [275] = https://curl.se/bug/?i=23084 + [279] = https://curl.se/bug/?i=23112 + [282] = https://curl.se/bug/?i=23111 + [289] = https://curl.se/bug/?i=23104 + [290] = https://curl.se/bug/?i=23102 + [291] = https://curl.se/bug/?i=23100 + [292] = https://curl.se/bug/?i=23099 + [296] = https://curl.se/bug/?i=23094 + [297] = https://curl.se/bug/?i=23091 + [298] = https://curl.se/bug/?i=23092 + [299] = https://curl.se/bug/?i=23090 From 5c3a833e66c3244797bd36b453ee29f3259798b7 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 5 Oct 2026 00:21:23 +0000 Subject: [PATCH 385/481] GHA: update dependency pizlonator/fil-c to v0.686 Closes #23257 --- .github/workflows/linux.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 934c4a9b8f42..e67603e33af2 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -42,7 +42,7 @@ env: # renovate: datasource=github-tags depName=google/boringssl versioning=semver registryUrl=https://github.com BORINGSSL_VERSION: 0.20260929.0 # renovate: datasource=github-releases depName=pizlonator/fil-c versioning=semver-coerced registryUrl=https://github.com - FIL_C_VERSION: 0.685 + FIL_C_VERSION: 0.686 # renovate: datasource=github-tags depName=libressl/portable versioning=semver registryUrl=https://github.com LIBRESSL_VERSION: 4.3.3 # renovate: datasource=github-tags depName=Mbed-TLS/mbedtls versioning=semver registryUrl=https://github.com From efcbd5607896d509c6fd77a740b8f8eb9f7586b6 Mon Sep 17 00:00:00 2001 From: Stefan Eissing Date: Thu, 1 Oct 2026 14:25:25 +0200 Subject: [PATCH 386/481] docs: SSL session import/file updates Clarify that exported SSL session are linked to configured file paths and not the files contents. Give advice how changes to CA/CRL files may be handled or what risk is involved. Closes #23220 --- docs/cmdline-opts/ssl-sessions.md | 11 +++++++++++ docs/libcurl/curl_easy_ssls_import.md | 12 ++++++++++++ 2 files changed, 23 insertions(+) diff --git a/docs/cmdline-opts/ssl-sessions.md b/docs/cmdline-opts/ssl-sessions.md index 33ef984a3ec4..8a0cc7da69c3 100644 --- a/docs/cmdline-opts/ssl-sessions.md +++ b/docs/cmdline-opts/ssl-sessions.md @@ -37,3 +37,14 @@ still check if a specific hostname matches one of the values. This feature requires that the underlying libcurl was built with the experimental SSL session import/export feature (SSLS-EXPORT) enabled. + +The sessions in the file identify origin and used TLS configurations. +Configuration parameters identified by a file path are only matched to the +path, not the file's content. Updates to CA, CRL or key file contents do +not invalidate previously saved sessions. + +Most TLS sessions handed out by servers have a short lifetime, which +limits the risk. Also, curl does not persist TLS session in this file +that are tied to authentication, e.g. a client certificate. If that all +is not acceptable, ssl session files should be cleared when older than +the CA/CRL file used or not used at all. diff --git a/docs/libcurl/curl_easy_ssls_import.md b/docs/libcurl/curl_easy_ssls_import.md index 3e43972f7053..c15498459148 100644 --- a/docs/libcurl/curl_easy_ssls_import.md +++ b/docs/libcurl/curl_easy_ssls_import.md @@ -44,6 +44,18 @@ Import of session tickets from other curl versions may fail due to changes in the handling of **shmac** or **sdata**. A session ticket which has already expired is silently discarded. +The session key identifies origin and used TLS configurations. Configuration +parameters identified by a file path are only matched to the path, not the +file's content. It is the applications responsibility to handle updates to +CA, CRL or key files, e.g. discarding previous sessions. Otherwise, +imported sessions may establish trusted connections to servers that would fail +to verify otherwise. + +Most TLS sessions handed out by servers have a short lifetime. If +continued use of these sessions is acceptable can only be decided by +the application. Also, TLS session that are tied to client authentication, +e.g. a client certificate, are never exported by libcurl. + # %PROTOCOLS% # EXAMPLE From 1c9f1f4c3f88a8704893fad117db085ed40da89a Mon Sep 17 00:00:00 2001 From: Stefan Eissing Date: Thu, 1 Oct 2026 11:45:13 +0200 Subject: [PATCH 387/481] dynhds: check size overflows When adding headers, check arguments and make sure lengths do not overflow size_t calculations. When formatting headers, use individual dynbuf adds instead of %.*s formats that require int casts. Reported-by: Joshua Rogers Closes #23210 --- lib/dynhds.c | 42 +++++++++++++++++++++++++++++------------- tests/unit/unit2602.c | 8 ++++++++ 2 files changed, 37 insertions(+), 13 deletions(-) diff --git a/lib/dynhds.c b/lib/dynhds.c index 3d00b1510098..08d0f8289512 100644 --- a/lib/dynhds.c +++ b/lib/dynhds.c @@ -33,8 +33,9 @@ static struct dynhds_entry *entry_new(const char *name, size_t namelen, struct dynhds_entry *e; char *p; - DEBUGASSERT(name); - DEBUGASSERT(value); + /* namelen + valuelen <= SIZE_MAX has already been checked by caller */ + if((SIZE_MAX - namelen - valuelen) < (sizeof(*e) + 2)) + return NULL; e = curlx_calloc(1, sizeof(*e) + namelen + valuelen + 2); if(!e) return NULL; @@ -42,8 +43,10 @@ static struct dynhds_entry *entry_new(const char *name, size_t namelen, memcpy(p, name, namelen); e->namelen = namelen; e->value = p += namelen + 1; /* leave a \0 at the end of name */ - memcpy(p, value, valuelen); - e->valuelen = valuelen; + if(valuelen) { + memcpy(p, value, valuelen); + e->valuelen = valuelen; + } if(opts & DYNHDS_OPT_LOWERCASE) Curl_strntolower(e->name, e->name, e->namelen); return e; @@ -135,10 +138,15 @@ CURLcode Curl_dynhds_add(struct dynhds *dynhds, struct dynhds_entry *entry = NULL; CURLcode result = CURLE_OUT_OF_MEMORY; - DEBUGASSERT(dynhds); - if(dynhds->max_entries && dynhds->hds_len >= dynhds->max_entries) - return CURLE_OUT_OF_MEMORY; - if(dynhds->strs_len + namelen + valuelen > dynhds->max_strs_size) + /* Accept NULL value with length 0, name must have length */ + if(!dynhds || !name || !namelen || (!value && valuelen)) { + DEBUGASSERT(0); + return CURLE_BAD_FUNCTION_ARGUMENT; + } + /* Does the new header fit into the limits? */ + if((dynhds->max_entries && (dynhds->hds_len >= dynhds->max_entries)) || + (dynhds->max_strs_size - dynhds->strs_len < namelen) || + (dynhds->max_strs_size - dynhds->strs_len - namelen < valuelen)) return CURLE_OUT_OF_MEMORY; entry = entry_new(name, namelen, value, valuelen, dynhds->opts); @@ -177,7 +185,10 @@ CURLcode Curl_dynhds_add(struct dynhds *dynhds, CURLcode Curl_dynhds_cadd(struct dynhds *dynhds, const char *name, const char *value) { - return Curl_dynhds_add(dynhds, name, strlen(name), value, strlen(value)); + if(!name) + return CURLE_BAD_FUNCTION_ARGUMENT; + return Curl_dynhds_add(dynhds, name, strlen(name), + value, value ? strlen(value) : 0); } CURLcode Curl_dynhds_h1_add_line(struct dynhds *dynhds, @@ -347,10 +358,15 @@ CURLcode Curl_dynhds_h1_dprint(struct dynhds *dynhds, struct dynbuf *dbuf) return result; for(i = 0; i < dynhds->hds_len; ++i) { - result = curlx_dyn_addf(dbuf, "%.*s: %.*s\r\n", - (int)dynhds->hds[i]->namelen, dynhds->hds[i]->name, - (int)dynhds->hds[i]->valuelen, - dynhds->hds[i]->value); + result = curlx_dyn_addn(dbuf, dynhds->hds[i]->name, + dynhds->hds[i]->namelen); + if(!result) + result = curlx_dyn_addn(dbuf, STRCONST(": ")); + if(!result) + result = curlx_dyn_addn(dbuf, dynhds->hds[i]->value, + dynhds->hds[i]->valuelen); + if(!result) + result = curlx_dyn_addn(dbuf, STRCONST("\r\n")); if(result) break; } diff --git a/tests/unit/unit2602.c b/tests/unit/unit2602.c index 6da472787ec6..c00a3f3d5c71 100644 --- a/tests/unit/unit2602.c +++ b/tests/unit/unit2602.c @@ -74,6 +74,10 @@ static CURLcode test_unit2602(const char *arg) fail_unless(dynhds_ccount_name(&hds, "TEST2") == 1, "should"); fail_unless(dynhds_ccontains(&hds, "TesT2"), "should"); fail_unless(dynhds_contains(&hds, "TeSt2", 5), "should"); + + /* Check overflow detection */ + fail_unless(Curl_dynhds_add(&hds, "test1", SIZE_MAX / 2, + "123", SIZE_MAX / 2), "overflow check failed"); Curl_dynhds_free(&hds); /* add header exceeding max overall length */ @@ -87,6 +91,10 @@ static CURLcode test_unit2602(const char *arg) Curl_dynhds_init(&hds, 128, 4 * 1024); fail_if(Curl_dynhds_add(&hds, "test1", 5, "123", 3), "add failed"); fail_if(Curl_dynhds_add(&hds, "test1", 5, "123", 3), "add failed"); + fail_if(Curl_dynhds_add(&hds, "test2", 5, NULL, 0), "add failed"); + fail_if(Curl_dynhds_cadd(&hds, "test2", NULL), "add failed"); + fail_unless(dynhds_ccontains(&hds, "test2"), "should"); + fail_unless(dynhds_ccount_name(&hds, "test2") == 2, "should"); fail_if(Curl_dynhds_cadd(&hds, "blablabla", "thingies"), "add failed"); fail_if(Curl_dynhds_h1_cadd_line(&hds, "blablabla: thingies"), "add failed"); fail_unless(dynhds_ccount_name(&hds, "blablabla") == 2, "should"); From 7f0bc7ad1faf4854c1e8aae9af91b0eafc3b90c9 Mon Sep 17 00:00:00 2001 From: Viktor Szakats Date: Mon, 5 Oct 2026 13:03:44 +0200 Subject: [PATCH 388/481] GHA: enable Renovate bumps for previous versions of mbedTLS and OpenSSL Tested locally with renovate CLI. Closes #23263 --- .github/workflows/http3-linux.yml | 5 ++--- .github/workflows/linux.yml | 5 ++--- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/.github/workflows/http3-linux.yml b/.github/workflows/http3-linux.yml index ddb69e9820d9..0c8d66a6c04c 100644 --- a/.github/workflows/http3-linux.yml +++ b/.github/workflows/http3-linux.yml @@ -51,9 +51,8 @@ env: LIBRESSL_VERSION: 4.3.3 # renovate: datasource=github-releases depName=openssl/openssl versioning=semver extractVersion=^openssl-(?.+)$ registryUrl=https://github.com OPENSSL_VERSION: 4.0.3 - # manually bumped + # renovate: datasource=github-releases depName=openssl/openssl versioning=semver extractVersion=^openssl-(?3\..+)$ registryUrl=https://github.com OPENSSL_PREV_VERSION: 3.6.5 - OPENSSL_PREV_SHA256: a2157c2830efdec3788939b00c9b0638306d3f0bbb76dc4832ee503bb397df98 # renovate: datasource=github-tags depName=cloudflare/quiche versioning=semver registryUrl=https://github.com QUICHE_VERSION: 0.30.0 # renovate: datasource=github-tags depName=wolfSSL/wolfssl versioning=semver extractVersion=^v?(?.+)-stable$ registryUrl=https://github.com @@ -318,7 +317,7 @@ jobs: cd ~ curl --disable --fail --silent --show-error --connect-timeout 15 --max-time 120 --retry 6 --retry-connrefused \ --location --proto-redir =https "https://github.com/openssl/openssl/releases/download/openssl-${OPENSSL_PREV_VERSION}/openssl-${OPENSSL_PREV_VERSION}.tar.gz" --output pkg.bin - sha256sum pkg.bin | tee /dev/stderr | grep -qwF -- "${OPENSSL_PREV_SHA256}" && tar -xzf pkg.bin && rm -f pkg.bin + sha256sum pkg.bin && tar -xzf pkg.bin && rm -f pkg.bin cd "openssl-${OPENSSL_PREV_VERSION}" ./config --prefix=/home/runner/pkg/openssl-prev --libdir=lib no-makedepend no-apps no-docs no-tests make diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index e67603e33af2..76b8a1ae1bcb 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -47,9 +47,8 @@ env: LIBRESSL_VERSION: 4.3.3 # renovate: datasource=github-tags depName=Mbed-TLS/mbedtls versioning=semver registryUrl=https://github.com MBEDTLS_VERSION: 4.2.0 - # manually bumped + # renovate: datasource=github-tags depName=Mbed-TLS/mbedtls versioning=semver extractVersion=^v?(?3\..+)$ registryUrl=https://github.com MBEDTLS_PREV_VERSION: 3.6.7 - MBEDTLS_PREV_SHA256: a7e8bcbec0e6f761b4af24f25677626b35f762f68eef79c08677a363212d11f6 # renovate: datasource=github-tags depName=nghttp2/nghttp2 versioning=semver registryUrl=https://github.com NGHTTP2_VERSION: 1.70.0 # handled in renovate.json @@ -675,7 +674,7 @@ jobs: run: | curl --disable --fail --silent --show-error --connect-timeout 15 --max-time 120 --retry 6 --retry-connrefused \ --location --proto-redir =https "https://github.com/Mbed-TLS/mbedtls/releases/download/mbedtls-${MBEDTLS_PREV_VERSION}/mbedtls-${MBEDTLS_PREV_VERSION}.tar.bz2" --output pkg.bin - sha256sum pkg.bin | tee /dev/stderr | grep -qwF -- "${MBEDTLS_PREV_SHA256}" && tar -xjf pkg.bin && rm -f pkg.bin + sha256sum pkg.bin && tar -xjf pkg.bin && rm -f pkg.bin cd "mbedtls-${MBEDTLS_PREV_VERSION}" ./scripts/config.py set MBEDTLS_THREADING_C ./scripts/config.py set MBEDTLS_THREADING_PTHREAD From 307871f7207feb854aac70b393686e5ed1257976 Mon Sep 17 00:00:00 2001 From: Viktor Szakats Date: Mon, 5 Oct 2026 13:10:17 +0200 Subject: [PATCH 389/481] GHA/http3-linux: downgrade to OpenSSL 3.5.x LTS (from 3.6.x) Ref: https://openssl-library.org/post/2025-02-20-openssl-3.5-lts/ Closes #23264 --- .github/workflows/http3-linux.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/http3-linux.yml b/.github/workflows/http3-linux.yml index 0c8d66a6c04c..ae872245ce71 100644 --- a/.github/workflows/http3-linux.yml +++ b/.github/workflows/http3-linux.yml @@ -51,8 +51,8 @@ env: LIBRESSL_VERSION: 4.3.3 # renovate: datasource=github-releases depName=openssl/openssl versioning=semver extractVersion=^openssl-(?.+)$ registryUrl=https://github.com OPENSSL_VERSION: 4.0.3 - # renovate: datasource=github-releases depName=openssl/openssl versioning=semver extractVersion=^openssl-(?3\..+)$ registryUrl=https://github.com - OPENSSL_PREV_VERSION: 3.6.5 + # renovate: datasource=github-releases depName=openssl/openssl versioning=semver extractVersion=^openssl-(?3\.5\..+)$ registryUrl=https://github.com + OPENSSL_PREV_VERSION: 3.5.9 # renovate: datasource=github-tags depName=cloudflare/quiche versioning=semver registryUrl=https://github.com QUICHE_VERSION: 0.30.0 # renovate: datasource=github-tags depName=wolfSSL/wolfssl versioning=semver extractVersion=^v?(?.+)-stable$ registryUrl=https://github.com From 30f5a2b5b8097eaa2941b504c761df445e8b006b Mon Sep 17 00:00:00 2001 From: Viktor Szakats Date: Mon, 5 Oct 2026 13:36:27 +0200 Subject: [PATCH 390/481] renovate: fix bump rule for OpenLDAP Tested locally with renovate CLI. Also: drop redundant `registryUrl=` for the new rule, to keep the line fit spacecheck.pl limits. A Renovate bump PR is expected after merging this. Follow-up to 4910ee0dc35a3dd231b8c5975bb3eec9dd42c16f Closes #23266 --- .github/renovate.json | 13 ------------- .github/workflows/linux.yml | 2 +- 2 files changed, 1 insertion(+), 14 deletions(-) diff --git a/.github/renovate.json b/.github/renovate.json index 86a85e41eb98..effd1a332273 100644 --- a/.github/renovate.json +++ b/.github/renovate.json @@ -79,19 +79,6 @@ ], "versioningTemplate": "{{#if versioning}}{{{versioning}}}{{else}}semver{{/if}}", "extractVersionTemplate": "{{#if extractVersion}}{{{extractVersion}}}{{else}}^v?(?.+)${{/if}}" - }, - { - "customType": "regex", - "managerFilePatterns": [ - "/^\\.github/workflows/linux\\.yml$/" - ], - "matchStrings": [ - "OPENLDAP_VERSION: (?.*)\\s" - ], - "datasourceTemplate": "github-tags", - "depNameTemplate": "openldap/openldap", - "versioningTemplate": "semver", - "extractVersionTemplate": "^OPENLDAP_REL_ENG_(?\\d+)_(?\\d+)_(?\\d+)$" } ] } diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 76b8a1ae1bcb..b47a41056ba6 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -51,7 +51,7 @@ env: MBEDTLS_PREV_VERSION: 3.6.7 # renovate: datasource=github-tags depName=nghttp2/nghttp2 versioning=semver registryUrl=https://github.com NGHTTP2_VERSION: 1.70.0 - # handled in renovate.json + # renovate: datasource=github-tags depName=openldap/openldap versioning=regex:^(?\d+)_(?\d+)_(?\d+)$ extractVersion=^OPENLDAP_REL_ENG_(?.+)$ OPENLDAP_VERSION: 2.6.10 # renovate: datasource=github-releases depName=openssl/openssl versioning=semver extractVersion=^openssl-(?.+)$ registryUrl=https://github.com OPENSSL_VERSION: 4.0.3 From 13f4a3695068a7fe53f92f635a6bd24d9dbc89a4 Mon Sep 17 00:00:00 2001 From: Viktor Szakats Date: Mon, 5 Oct 2026 13:48:39 +0200 Subject: [PATCH 391/481] renovate: drop redundant annotation tags - drop `versioning=semver`, it's the default fallback in `renovate.json`. - drop redundant `registryUrl=https://github.com`. It's the default when using `datasource=github-tags` or `datasource=github-releases` Refs: https://docs.renovatebot.com/modules/datasource/github-releases/ https://docs.renovatebot.com/modules/datasource/github-tags/ Tested locally with renovate CLI. Closes #23268 --- .github/workflows/http3-linux.yml | 24 ++++++++++++------------ .github/workflows/linux.yml | 20 ++++++++++---------- .github/workflows/macos.yml | 2 +- .github/workflows/non-native.yml | 2 +- 4 files changed, 24 insertions(+), 24 deletions(-) diff --git a/.github/workflows/http3-linux.yml b/.github/workflows/http3-linux.yml index ae872245ce71..2d85ab9aebfe 100644 --- a/.github/workflows/http3-linux.yml +++ b/.github/workflows/http3-linux.yml @@ -39,29 +39,29 @@ env: CURL_CI: github CURL_TEST_MIN: 2000 DO_NOT_TRACK: '1' - # renovate: datasource=github-tags depName=awslabs/aws-lc versioning=semver registryUrl=https://github.com + # renovate: datasource=github-tags depName=awslabs/aws-lc AWSLC_VERSION: 5.8.0 - # renovate: datasource=github-tags depName=google/boringssl versioning=semver registryUrl=https://github.com + # renovate: datasource=github-tags depName=google/boringssl BORINGSSL_VERSION: 0.20260929.0 - # renovate: datasource=github-tags depName=gnutls/nettle versioning=semver-coerced extractVersion=^nettle_?(?.+)_release_.+$ registryUrl=https://github.com + # renovate: datasource=github-tags depName=gnutls/nettle versioning=semver-coerced extractVersion=^nettle_?(?.+)_release_.+$ NETTLE_VERSION: '4.0' - # renovate: datasource=github-tags depName=gnutls/gnutls versioning=semver registryUrl=https://github.com + # renovate: datasource=github-tags depName=gnutls/gnutls GNUTLS_VERSION: 3.8.13 - # renovate: datasource=github-tags depName=libressl/portable versioning=semver registryUrl=https://github.com + # renovate: datasource=github-tags depName=libressl/portable LIBRESSL_VERSION: 4.3.3 - # renovate: datasource=github-releases depName=openssl/openssl versioning=semver extractVersion=^openssl-(?.+)$ registryUrl=https://github.com + # renovate: datasource=github-releases depName=openssl/openssl extractVersion=^openssl-(?.+)$ OPENSSL_VERSION: 4.0.3 - # renovate: datasource=github-releases depName=openssl/openssl versioning=semver extractVersion=^openssl-(?3\.5\..+)$ registryUrl=https://github.com + # renovate: datasource=github-releases depName=openssl/openssl extractVersion=^openssl-(?3\.5\..+)$ OPENSSL_PREV_VERSION: 3.5.9 - # renovate: datasource=github-tags depName=cloudflare/quiche versioning=semver registryUrl=https://github.com + # renovate: datasource=github-tags depName=cloudflare/quiche QUICHE_VERSION: 0.30.0 - # renovate: datasource=github-tags depName=wolfSSL/wolfssl versioning=semver extractVersion=^v?(?.+)-stable$ registryUrl=https://github.com + # renovate: datasource=github-tags depName=wolfSSL/wolfssl extractVersion=^v?(?.+)-stable$ WOLFSSL_VERSION: 5.9.4 - # renovate: datasource=github-tags depName=ngtcp2/nghttp3 versioning=semver registryUrl=https://github.com + # renovate: datasource=github-tags depName=ngtcp2/nghttp3 NGHTTP3_VERSION: 1.18.0 - # renovate: datasource=github-tags depName=ngtcp2/ngtcp2 versioning=semver registryUrl=https://github.com + # renovate: datasource=github-tags depName=ngtcp2/ngtcp2 NGTCP2_VERSION: 1.25.0 - # renovate: datasource=github-tags depName=nghttp2/nghttp2 versioning=semver registryUrl=https://github.com + # renovate: datasource=github-tags depName=nghttp2/nghttp2 NGHTTP2_VERSION: 1.70.0 # no tagged releases H2O_VERSION: 11b0cfa2771e3ccad4a852e72473e4e278ab1de7 # 2026-05-28 diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index b47a41056ba6..e9cc142ef81c 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -37,27 +37,27 @@ env: CURL_CI: github CURL_TEST_MIN: 1800 DO_NOT_TRACK: '1' - # renovate: datasource=github-tags depName=awslabs/aws-lc versioning=semver registryUrl=https://github.com + # renovate: datasource=github-tags depName=awslabs/aws-lc AWSLC_VERSION: 5.8.0 - # renovate: datasource=github-tags depName=google/boringssl versioning=semver registryUrl=https://github.com + # renovate: datasource=github-tags depName=google/boringssl BORINGSSL_VERSION: 0.20260929.0 - # renovate: datasource=github-releases depName=pizlonator/fil-c versioning=semver-coerced registryUrl=https://github.com + # renovate: datasource=github-releases depName=pizlonator/fil-c versioning=semver-coerced FIL_C_VERSION: 0.686 - # renovate: datasource=github-tags depName=libressl/portable versioning=semver registryUrl=https://github.com + # renovate: datasource=github-tags depName=libressl/portable LIBRESSL_VERSION: 4.3.3 - # renovate: datasource=github-tags depName=Mbed-TLS/mbedtls versioning=semver registryUrl=https://github.com + # renovate: datasource=github-tags depName=Mbed-TLS/mbedtls MBEDTLS_VERSION: 4.2.0 - # renovate: datasource=github-tags depName=Mbed-TLS/mbedtls versioning=semver extractVersion=^v?(?3\..+)$ registryUrl=https://github.com + # renovate: datasource=github-tags depName=Mbed-TLS/mbedtls extractVersion=^v?(?3\..+)$ MBEDTLS_PREV_VERSION: 3.6.7 - # renovate: datasource=github-tags depName=nghttp2/nghttp2 versioning=semver registryUrl=https://github.com + # renovate: datasource=github-tags depName=nghttp2/nghttp2 NGHTTP2_VERSION: 1.70.0 # renovate: datasource=github-tags depName=openldap/openldap versioning=regex:^(?\d+)_(?\d+)_(?\d+)$ extractVersion=^OPENLDAP_REL_ENG_(?.+)$ OPENLDAP_VERSION: 2.6.10 - # renovate: datasource=github-releases depName=openssl/openssl versioning=semver extractVersion=^openssl-(?.+)$ registryUrl=https://github.com + # renovate: datasource=github-releases depName=openssl/openssl extractVersion=^openssl-(?.+)$ OPENSSL_VERSION: 4.0.3 - # renovate: datasource=github-tags depName=rustls/rustls-ffi versioning=semver registryUrl=https://github.com + # renovate: datasource=github-tags depName=rustls/rustls-ffi RUSTLS_VERSION: 0.15.4 - # renovate: datasource=github-tags depName=wolfSSL/wolfssl versioning=semver extractVersion=^v?(?.+)-stable$ registryUrl=https://github.com + # renovate: datasource=github-tags depName=wolfSSL/wolfssl extractVersion=^v?(?.+)-stable$ WOLFSSL_VERSION: 5.9.4 jobs: diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index 4f9b63f05adf..18f44015436a 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -65,7 +65,7 @@ jobs: LDFLAGS: '' MATRIX_BUILD: ${{ matrix.build.generate && 'cmake' || 'autotools' }} MATRIX_OPTIONS: ${{ matrix.build.options }} - # renovate: datasource=github-tags depName=libressl/portable versioning=semver registryUrl=https://github.com + # renovate: datasource=github-tags depName=libressl/portable LIBRESSL_VERSION: 4.3.3 strategy: fail-fast: false diff --git a/.github/workflows/non-native.yml b/.github/workflows/non-native.yml index aca4f6246626..851092e5deb2 100644 --- a/.github/workflows/non-native.yml +++ b/.github/workflows/non-native.yml @@ -493,7 +493,7 @@ jobs: LDFLAGS: -s MAKEFLAGS: -j 5 MATRIX_BUILD: '${{ matrix.build }}' - # renovate: datasource=github-releases depName=andrewwutw/build-djgpp versioning=semver-coerced registryUrl=https://github.com + # renovate: datasource=github-releases depName=andrewwutw/build-djgpp versioning=semver-coerced TOOLCHAIN_VERSION: '3.4' TOOLCHAIN_SHA256: 8464f17017d6ab1b2bb2df4ed82357b5bf692e6e2b7fee37e315638f3d505f00 strategy: From cc5ed559293da0efcb0b5e1a98f62dc010f858a7 Mon Sep 17 00:00:00 2001 From: Viktor Szakats Date: Mon, 5 Oct 2026 14:11:11 +0200 Subject: [PATCH 392/481] renovate: fix bump rule for OpenLDAP (cont.) Keep the version underscored on `OPENLDAP_VERSION`. Convert to dot on download. Found no way to convert it in cache key, therefore that changes to use underscores with this patch. Follow-up to 30f5a2b5b8097eaa2941b504c761df445e8b006b #23266 Closes #23269 --- .github/workflows/linux.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index e9cc142ef81c..4ec914468a47 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -52,7 +52,7 @@ env: # renovate: datasource=github-tags depName=nghttp2/nghttp2 NGHTTP2_VERSION: 1.70.0 # renovate: datasource=github-tags depName=openldap/openldap versioning=regex:^(?\d+)_(?\d+)_(?\d+)$ extractVersion=^OPENLDAP_REL_ENG_(?.+)$ - OPENLDAP_VERSION: 2.6.10 + OPENLDAP_VERSION: 2_6_10 # renovate: datasource=github-releases depName=openssl/openssl extractVersion=^openssl-(?.+)$ OPENSSL_VERSION: 4.0.3 # renovate: datasource=github-tags depName=rustls/rustls-ffi @@ -697,9 +697,9 @@ jobs: if: ${{ contains(matrix.build.install_steps, 'openldap-static') && !steps.cache1-openldap-static.outputs.cache-hit }} run: | curl --disable --fail --silent --show-error --connect-timeout 15 --max-time 120 --retry 6 --retry-connrefused \ - "https://www.openldap.org/software/download/OpenLDAP/openldap-release/openldap-${OPENLDAP_VERSION}.tgz" --output pkg.bin + "https://www.openldap.org/software/download/OpenLDAP/openldap-release/openldap-${OPENLDAP_VERSION//_/.}.tgz" --output pkg.bin sha256sum pkg.bin && tar -xzf pkg.bin && rm -f pkg.bin - cd "openldap-${OPENLDAP_VERSION}" + cd "openldap-${OPENLDAP_VERSION//_/.}" autoreconf -fi ./configure --prefix=/home/runner/pkg/openldap-static --enable-static --disable-shared --disable-slapd make install From 3685da5e4105bbb390681bf66d8c0a38189cb14d Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 5 Oct 2026 12:24:03 +0000 Subject: [PATCH 393/481] GHA: update dependency openldap/openldap to v2.7.1 Closes #23270 --- .github/workflows/linux.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 4ec914468a47..521133f5dc01 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -52,7 +52,7 @@ env: # renovate: datasource=github-tags depName=nghttp2/nghttp2 NGHTTP2_VERSION: 1.70.0 # renovate: datasource=github-tags depName=openldap/openldap versioning=regex:^(?\d+)_(?\d+)_(?\d+)$ extractVersion=^OPENLDAP_REL_ENG_(?.+)$ - OPENLDAP_VERSION: 2_6_10 + OPENLDAP_VERSION: 2_7_1 # renovate: datasource=github-releases depName=openssl/openssl extractVersion=^openssl-(?.+)$ OPENSSL_VERSION: 4.0.3 # renovate: datasource=github-tags depName=rustls/rustls-ffi From 52fc4acff44b7b03b47016723ca8ef8d193286f1 Mon Sep 17 00:00:00 2001 From: Daniel Stenberg Date: Mon, 5 Oct 2026 13:01:17 +0200 Subject: [PATCH 394/481] cookie: accept and ignore values for 'secure' and 'httponly' They are supposed to be plain `secure` and `httponly` (without any following equals sign) but with this change, variations with `secure=[value]` and `httponly=[value]` are accepted and the value to the right of the equals sign is ignored. Possibly supported by RFC 6265 section 5.2.5 and 5.2.6. curl used to accept this format in the past. Verified in test 31 and 414. Reported-by: Wonyoung Jung Closes #23265 --- lib/cookie.c | 60 +++++++++++++++++++--------------------------- tests/data/test31 | 2 +- tests/data/test414 | 2 +- 3 files changed, 26 insertions(+), 38 deletions(-) diff --git a/lib/cookie.c b/lib/cookie.c index 3c0ebee91ce0..ccfa58a07d02 100644 --- a/lib/cookie.c +++ b/lib/cookie.c @@ -448,29 +448,6 @@ static bool parse_first_pair(struct Curl_easy *data, struct Cookie *co, return TRUE; } -static bool parse_flag(struct Curl_easy *data, struct Cookie *co, - const struct CookieInfo *ci, - struct Curl_str *name, bool secure) -{ - /* - * secure cookies are only allowed to be set when the connection is - * using a secure protocol, or when the cookie is being set by - * reading from file - */ - if(curlx_str_casecompare(name, "secure")) { - if(secure || !ci->running) - co->secure = TRUE; - else { - infof(data, "skipped cookie because not 'secure'"); - return FALSE; - } - } - else if(curlx_str_casecompare(name, "httponly")) - co->httponly = TRUE; - - return TRUE; -} - static bool parse_domain(struct Curl_easy *data, struct Cookie *co, struct Curl_str *cookie_domain, struct Curl_str *val, @@ -587,7 +564,6 @@ static void parse_expires(struct Cookie *co, struct Curl_str *val, static CURLcode parse_cookie_header(struct Curl_easy *data, struct Cookie *co, - const struct CookieInfo *ci, bool *okay, /* if the cookie was fine */ const char *ptr, /* the header */ const char *domain, /* default domain */ @@ -638,20 +614,32 @@ parse_cookie_header(struct Curl_easy *data, if(!parse_first_pair(data, co, cookie, &name, &val, sep)) return CURLE_OK; } - else if(!sep) { - if(!parse_flag(data, co, ci, &name, secure_origin)) + + /* whether there's separator or not */ + else if(curlx_str_casecompare(&name, "secure")) { + if(secure_origin) + co->secure = TRUE; + else { + infof(data, "skipped cookie because not 'secure'"); return CURLE_OK; + } } - else if(curlx_str_casecompare(&name, "path")) - cookie[COOKIE_PATH] = val; - else if(curlx_str_casecompare(&name, "domain") && curlx_strlen(&val)) { - if(!parse_domain(data, co, &cookie[COOKIE_DOMAIN], &val, &domain)) - return CURLE_OK; + else if(curlx_str_casecompare(&name, "httponly")) + co->httponly = TRUE; + else if(sep) { + /* only if a separating equals sign is present */ + if(curlx_str_casecompare(&name, "path")) + cookie[COOKIE_PATH] = val; + else if(curlx_str_casecompare(&name, "domain")) { + if(curlx_strlen(&val) && + !parse_domain(data, co, &cookie[COOKIE_DOMAIN], &val, &domain)) + return CURLE_OK; + } + else if(curlx_str_casecompare(&name, "max-age") && curlx_strlen(&val)) + parse_maxage(co, &val, &now); + else if(curlx_str_casecompare(&name, "expires") && curlx_strlen(&val)) + parse_expires(co, &val, &now); } - else if(curlx_str_casecompare(&name, "max-age") && curlx_strlen(&val)) - parse_maxage(co, &val, &now); - else if(curlx_str_casecompare(&name, "expires") && curlx_strlen(&val)) - parse_expires(co, &val, &now); } } while(!curlx_str_single(&ptr, ';')); @@ -1018,7 +1006,7 @@ CURLcode Curl_cookie_add(struct Curl_easy *data, memset(co, 0, sizeof(comem)); if(flags & COOKIE_HTTPHEADER) - result = parse_cookie_header(data, co, ci, &okay, + result = parse_cookie_header(data, co, &okay, lineptr, domain, path, flags & COOKIE_SECURE); else result = parse_netscape(co, ci, &okay, lineptr, flags & COOKIE_SECURE); diff --git a/tests/data/test31 b/tests/data/test31 index c885b7c9faed..49e9a0c2ffe2 100644 --- a/tests/data/test31 +++ b/tests/data/test31 @@ -39,7 +39,7 @@ Set-Cookie: secure=very1 ; secure; domain=test31.curl; path=/secure9/; %CR Set-Cookie: httpo1=value1 ; domain=test31.curl; path=/p1/; httponly%CR Set-Cookie: httpo2=value2 ; domain=test31.curl; path=/p2/; httponly%CR Set-Cookie: httpo3=value3 ; httponly; domain=test31.curl; path=/p3/;%CR -Set-Cookie: httpo4=value4 ; httponly; domain=test31.curl; path=/p4/; %CR +Set-Cookie: httpo4=value4 ; httponly =rubbish; domain=test31.curl; path=/p4/; %CR Set-Cookie: httponly=myvalue1 ; domain=test31.curl; path=/p4/; httponly%CR Set-Cookie: httpandsec=myvalue2 ; domain=test31.curl; path=/p4/; httponly; secure%CR Set-Cookie: httpandsec2=myvalue3; domain=test31.curl; path=/p4/; httponly; secure%CR diff --git a/tests/data/test414 b/tests/data/test414 index d04426e060bd..a357ecd9dd54 100644 --- a/tests/data/test414 +++ b/tests/data/test414 @@ -16,7 +16,7 @@ Date: Tue, 09 Nov 2010 14:49:00 GMT Server: test-server/fake Content-Length: 6 Set-Cookie: SESSIONID=originaltoken; secure -Set-Cookie: second=originaltoken; secure; path=/a +Set-Cookie: second=originaltoken; secure = 1; path=/a Location: http://attack.invalid:%HTTPPORT/a/b/%TESTNUMBER0002 -foo- From b783adf8cf706f0c1c1a50f0812ad10de888dcb3 Mon Sep 17 00:00:00 2001 From: Viktor Szakats Date: Mon, 5 Oct 2026 14:44:48 +0200 Subject: [PATCH 395/481] GHA: auto-bump stunnel, amissl, add comments to manually bumped deps Bump AmiSSL once per year. stunnel may need fixups depending on how the next bump turns out, because we watch tags while the download comes from the homepage. Closes #23271 --- .github/renovate.json | 10 ++++++++++ .github/workflows/distcheck.yml | 2 +- .github/workflows/linux-old.yml | 4 ++-- .github/workflows/linux.yml | 6 +++--- .github/workflows/non-native.yml | 5 +++-- .github/workflows/windows.yml | 18 +++++++++--------- appveyor.yml | 14 +++++++------- 7 files changed, 35 insertions(+), 24 deletions(-) diff --git a/.github/renovate.json b/.github/renovate.json index effd1a332273..a11837b183f0 100644 --- a/.github/renovate.json +++ b/.github/renovate.json @@ -65,6 +65,16 @@ "schedule": [ "* * 10 * *" ] + }, + { + "description": "Schedule package updates on the 10th of January", + "matchSourceUrls": [ + "**/amissl/**" + ], + "groupName": "yearly updates by URL", + "schedule": [ + "* * 10 1 *" + ] } ], "customManagers": [ diff --git a/.github/workflows/distcheck.yml b/.github/workflows/distcheck.yml index cec6c4ee3b12..390a7309b1f8 100644 --- a/.github/workflows/distcheck.yml +++ b/.github/workflows/distcheck.yml @@ -262,7 +262,7 @@ jobs: MAKEFLAGS: ${{ contains(matrix.image, 'macos') && '-j 4' || '-j 5' }} MATRIX_IMAGE: '${{ matrix.image }}' TESTOPTS: ${{ contains(matrix.image, 'macos') && '-D_CURL_PREFILL=ON' || '' }} ${{ contains(matrix.image, 'windows') && '-DCMAKE_UNITY_BUILD_BATCH_SIZE=30' || '' }} - OLD_CMAKE_VERSION: 3.19.8 + OLD_CMAKE_VERSION: 3.19.8 # manually bumped OLD_CMAKE_SHA256_LINUX_ARM: 807f5afb2a560e00af9640e496d5673afefc2888bf0ed076412884a5ebb547a1 OLD_CMAKE_SHA256_MACOS_UNI: 0976d23d982af05dcbfb3aa34fcb62ead43bea27f0e3bb95222f2a78161423f2 OLD_CMAKE_SHA256_WIN_INTEL: 2a30877a3d6b50da305b289f4d1c03befdfaeb2edba02a563c681e883d810380 diff --git a/.github/workflows/linux-old.yml b/.github/workflows/linux-old.yml index 6279ca2d71ca..a41a1f10a676 100644 --- a/.github/workflows/linux-old.yml +++ b/.github/workflows/linux-old.yml @@ -59,7 +59,7 @@ jobs: cmake-autotools: name: 'autotools & cmake' runs-on: ubuntu-latest - container: debian:stretch-20220622-slim@sha256:c5cd3ffceeb25b683bf5111ea89bf8049a177e00fb237235d48076a19cc80097 + container: debian:stretch-20220622-slim@sha256:c5cd3ffceeb25b683bf5111ea89bf8049a177e00fb237235d48076a19cc80097 # manually bumped (oldest supported Debian major version, rule disabled) steps: - name: 'install prereqs' @@ -84,7 +84,7 @@ jobs: - name: 'install prereqs (cmake)' env: - CMAKE_VERSION: 3.18.0 # Earliest version supported by curl + CMAKE_VERSION: 3.18.0 # manually bumped (lowest minor version supported by curl) CMAKE_SHA256: 4d9a9d3351161073a67e49366d701b6fa4b0343781982dc5eef08a02a750d403 run: | cd ~ diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 521133f5dc01..25ead485364f 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -470,15 +470,15 @@ jobs: # Current curl no longer supports OpenSSL 1.1.0, thus build with GnuTLS instead: configure: --enable-debug --with-gnutls --with-libssh2 --with-gssapi --enable-ares --without-ca-bundle --with-ca-path=/etc/ssl/certs # Docker Hub image that `container-job` executes in - container: andy5995/slackware-build-essential:15.0@sha256:c5aeede463b4a52b5f4650078418718f2447bdb23f9a4cac0d5fb262e7b8dc84 + container: andy5995/slackware-build-essential:15.0@sha256:c5aeede463b4a52b5f4650078418718f2447bdb23f9a4cac0d5fb262e7b8dc84 # manually bumped - name: 'Alpine MUSL https-rr' configure: --enable-debug --with-ssl --with-libssh2 --with-libidn2 --with-gssapi --enable-ldap --with-libpsl --enable-httpsrr --enable-ares --enable-threaded-resolver - container: alpine:3.24@sha256:294b683cb724975bec92580e1e685676bd4b50bda910ddb8c51d4cabeaec77e6 # 3.24.2 + container: alpine:3.24@sha256:294b683cb724975bec92580e1e685676bd4b50bda910ddb8c51d4cabeaec77e6 # 3.24.2 (manually bumped) - name: 'Alpine MUSL https-rr c-ares' configure: --enable-debug --with-ssl --with-libssh2 --with-libidn2 --with-gssapi --enable-ldap --with-libpsl --enable-httpsrr --enable-ares --disable-threaded-resolver - container: alpine:3.20@sha256:d9e853e87e55526f6b2917df91a2115c36dd7c696a35be12163d44e6e2a4b6bc # 3.20.10 + container: alpine:3.20@sha256:d9e853e87e55526f6b2917df91a2115c36dd7c696a35be12163d44e6e2a4b6bc # 3.20.10 (manually bumped, a previous minor version) steps: - name: 'install prereqs' diff --git a/.github/workflows/non-native.yml b/.github/workflows/non-native.yml index 851092e5deb2..da2187318038 100644 --- a/.github/workflows/non-native.yml +++ b/.github/workflows/non-native.yml @@ -262,9 +262,10 @@ jobs: env: MAKEFLAGS: -j 5 MATRIX_BUILD: '${{ matrix.build }}' - AMISSL_VERSION: '5.27' + # renovate: datasource=github-releases depName=jens-maus/amissl versioning=semver-coerced + AMISSL_VERSION: 5.27 AMISSL_SHA256: 5003bef8c5930354d16b0ce7196d71b2811891c42fad38a9238c5ce4098ad42a - TOOLCHAIN_VERSION: 6.5.0 + TOOLCHAIN_VERSION: 6.5.0 # manually bumped TOOLCHAIN_SHA256: 381e227c9ef552f073771d6f851cfdf873b574f3cf5db7c1c0107ea5d7146edc strategy: matrix: diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index d7fab768f275..a4c825df9d39 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -39,11 +39,11 @@ env: CURL_TEST_MIN: 1900 CURL_TEST_SSH_KEYALGO: ed25519 DO_NOT_TRACK: '1' - OPENSSH_WINDOWS_VERSION: 10.0.0.0p2-Preview + OPENSSH_WINDOWS_VERSION: 10.0.0.0p2-Preview # manually bumped OPENSSH_WINDOWS_SHA256_ARM64: 698c6aec31c1dd0fb996206e8741f4531a97355686b5431ef347d531b07fcd42 OPENSSH_WINDOWS_SHA256_WIN64: 23f50f3458c4c5d0b12217c6a5ddfde0137210a30fa870e98b29827f7b43aba5 - STUNNEL_VERSION: '5.82' - STUNNEL_SHA256: 722c1ad626919af2b0034666b4d19cc4795e925c32f60e8f3c59af4fb3ad2dfc + # renovate: datasource=github-tags depName=mtrojnar/stunnel versioning=semver-coerced extractVersion=^stunnel-(?.+)$ + STUNNEL_VERSION: 5.82 jobs: build-cache: @@ -69,7 +69,7 @@ jobs: cd /c && mkdir my-stunnel && cd my-stunnel curl --disable --fail --silent --show-error --connect-timeout 15 --max-time 240 --retry 3 --retry-connrefused \ "https://www.stunnel.org/archive/5.x/stunnel-${STUNNEL_VERSION}-win64-installer.exe" --output pkg.bin - sha256sum pkg.bin && sha256sum pkg.bin | grep -qwF -- "${STUNNEL_SHA256}" && 7z x -y pkg.bin >/dev/null && rm -f pkg.bin && ls -l && bin/tstunnel -version + sha256sum pkg.bin && 7z x -y pkg.bin >/dev/null && rm -f pkg.bin && ls -l && bin/tstunnel -version cygwin: name: "cygwin, ${{ matrix.build == 'cmake' && 'CM' || 'AM' }} ${{ matrix.platform }} ${{ matrix.name }}" @@ -604,7 +604,7 @@ jobs: dir: 'w64devkit' env: 'x86_64' ver: '16.2.0' - url: 'https://github.com/skeeto/w64devkit/releases/download/v2.10.0/w64devkit-x64-2.10.0.7z.exe' + url: 'https://github.com/skeeto/w64devkit/releases/download/v2.10.0/w64devkit-x64-2.10.0.7z.exe' # manually bumped SHA256: 18d0a4c71a166f8401ab6305781bec5882b40b5e06ba9807c61cb5f3b3c6325e config: '-DENABLE_DEBUG=ON -DBUILD_SHARED_LIBS=OFF -DCURL_USE_SCHANNEL=ON -DCURL_USE_LIBSSH=ON -DENABLE_UNICODE=OFF -DENABLE_UNIX_SOCKETS=OFF -DCURL_GCC_ANALYZER=ON -DCMAKE_C_FLAGS=-DCURL_DEBUG_NO_WIN32_WND' install: perl mingw-w64-x86_64-libssh @@ -614,7 +614,7 @@ jobs: dir: 'mingw64' env: 'x86_64' ver: '9.5.0' - url: 'https://github.com/brechtsanders/winlibs_mingw/releases/download/9.5.0-10.0.0-msvcrt-r1/winlibs-x86_64-posix-seh-gcc-9.5.0-mingw-w64msvcrt-10.0.0-r1.7z' + url: 'https://github.com/brechtsanders/winlibs_mingw/releases/download/9.5.0-10.0.0-msvcrt-r1/winlibs-x86_64-posix-seh-gcc-9.5.0-mingw-w64msvcrt-10.0.0-r1.7z' # manually bumped SHA256: 41637132ea7dc36a7f86a1961eaa334c380b5a3423d36aecb481cabcd006e3fe config: '-DBUILD_SHARED_LIBS=OFF -DCURL_USE_SCHANNEL=ON -DENABLE_UNICODE=OFF -DCURL_DISABLE_VERBOSE_STRINGS=ON -DCMAKE_C_FLAGS=-DCURL_DEBUG_NO_WIN32_WND' type: 'Release' @@ -624,7 +624,7 @@ jobs: dir: 'mingw64' env: 'x86_64' ver: '7.3.0' - url: 'https://downloads.sourceforge.net/mingw-w64/Toolchains%20targetting%20Win64/Personal%20Builds/mingw-builds/7.3.0/threads-win32/seh/x86_64-7.3.0-release-win32-seh-rt_v5-rev0.7z' + url: 'https://downloads.sourceforge.net/mingw-w64/Toolchains%20targetting%20Win64/Personal%20Builds/mingw-builds/7.3.0/threads-win32/seh/x86_64-7.3.0-release-win32-seh-rt_v5-rev0.7z' # manually bumped SHA256: 9dc08c9c2bdd5d8173f87791bed644f6e290624f739de474f117b590dfd8a721 config: '-DENABLE_DEBUG=ON -DBUILD_SHARED_LIBS=OFF -DCURL_USE_SCHANNEL=ON -DENABLE_UNICODE=ON -DCURL_USE_MBEDTLS=ON -DCURL_TARGET_WINDOWS_VERSION=0x0600' install: mingw-w64-x86_64-mbedtls @@ -635,7 +635,7 @@ jobs: dir: 'mingw32' env: 'i686' ver: '6.4.0' - url: 'https://downloads.sourceforge.net/mingw-w64/Toolchains%20targetting%20Win32/Personal%20Builds/mingw-builds/6.4.0/threads-win32/dwarf/i686-6.4.0-release-win32-dwarf-rt_v5-rev0.7z' + url: 'https://downloads.sourceforge.net/mingw-w64/Toolchains%20targetting%20Win32/Personal%20Builds/mingw-builds/6.4.0/threads-win32/dwarf/i686-6.4.0-release-win32-dwarf-rt_v5-rev0.7z' # manually bumped SHA256: 12d2c62ad4527ec8a52275ea8485678dcbe20bec4716a3c7ba274f225d696085 config: '-DCURL_USE_GNUTLS=ON -DCURL_USE_OPENSSL=ON -DCURL_USE_SCHANNEL=ON -DENABLE_ARES=ON -DENABLE_UNICODE=OFF -DCMAKE_UNITY_BUILD=OFF -DCURL_TARGET_WINDOWS_VERSION=0x0600' install: perl mingw-w64-i686-c-ares mingw-w64-i686-gnutls mingw-w64-i686-libssh2 mingw-w64-i686-openssl @@ -646,7 +646,7 @@ jobs: dir: 'mingw64' env: 'x86_64' ver: '4.8.1' - url: 'https://downloads.sourceforge.net/mingw-w64/Toolchains%20targetting%20Win64/Personal%20Builds/mingw-builds/4.8.1/threads-win32/seh/x86_64-4.8.1-release-win32-seh-rt_v3-rev2.7z' + url: 'https://downloads.sourceforge.net/mingw-w64/Toolchains%20targetting%20Win64/Personal%20Builds/mingw-builds/4.8.1/threads-win32/seh/x86_64-4.8.1-release-win32-seh-rt_v3-rev2.7z' # manually bumped (lowest supported mingw-w64 version) SHA256: 1353d997e85bb4494ebbebb432d824848d66b32c6045900da9a38a767b3c4ab4 config: '-DENABLE_DEBUG=ON -DBUILD_SHARED_LIBS=OFF -DCURL_USE_SCHANNEL=ON -DCURL_TARGET_WINDOWS_VERSION=0x0600' type: 'Debug' diff --git a/appveyor.yml b/appveyor.yml index 25fceb67dba2..41cc3d5139b8 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -43,42 +43,42 @@ environment: - job_name: 'CM VS2026, Release, arm64, Schannel, Static, Build-tests' APPVEYOR_BUILD_WORKER_IMAGE: 'Visual Studio 2026' - CMAKE_VERSION: 4.2.1 + CMAKE_VERSION: 4.2.1 # manually bumped CMAKE_SHA256: dfc2b2afac257555e3b9ce375b12b2883964283a366c17fec96cf4d17e4f1677 CMAKE_GENERATOR: 'Visual Studio 18 2026' CMAKE_GENERATE: '-A ARM64 -DENABLE_DEBUG=OFF -DBUILD_SHARED_LIBS=OFF' - job_name: 'CM VS2010, Debug, x64, Schannel, Shared, Build-tests & examples' APPVEYOR_BUILD_WORKER_IMAGE: 'Visual Studio 2015' - CMAKE_VERSION: 3.18.4 + CMAKE_VERSION: 3.18.4 # manually bumped (lowest minor version supported by curl) CMAKE_SHA256: a932bc0c8ee79f1003204466c525b38a840424d4ae29f9e5fb88959116f2407d CMAKE_GENERATOR: 'Visual Studio 10 2010' CMAKE_GENERATE: '-A x64' - job_name: 'CM VS2012, Release, x86, Schannel, Shared, Build-tests' APPVEYOR_BUILD_WORKER_IMAGE: 'Visual Studio 2015' - CMAKE_VERSION: 3.21.7 + CMAKE_VERSION: 3.21.7 # manually bumped CMAKE_SHA256: 4c4840e2dc2bf82e8a16081ff506bba54f3a228b91ce36317129fed4035ef2e3 CMAKE_GENERATOR: 'Visual Studio 11 2012' CMAKE_GENERATE: '-A Win32 -DENABLE_DEBUG=OFF' - job_name: 'CM VS2013, Debug, x64, Schannel, Shared' APPVEYOR_BUILD_WORKER_IMAGE: 'Visual Studio 2015' - CMAKE_VERSION: 3.18.4 + CMAKE_VERSION: 3.18.4 # manually bumped (lowest minor version supported by curl) CMAKE_SHA256: a932bc0c8ee79f1003204466c525b38a840424d4ae29f9e5fb88959116f2407d CMAKE_GENERATOR: 'Visual Studio 12 2013' CMAKE_GENERATE: '-A x64' - job_name: 'CM VS2015, Debug, x64, Schannel, Static' APPVEYOR_BUILD_WORKER_IMAGE: 'Visual Studio 2015' - CMAKE_VERSION: 3.19.8 + CMAKE_VERSION: 3.19.8 # manually bumped CMAKE_SHA256: 2a30877a3d6b50da305b289f4d1c03befdfaeb2edba02a563c681e883d810380 CMAKE_GENERATOR: 'Visual Studio 14 2015' CMAKE_GENERATE: '-A x64 -DBUILD_SHARED_LIBS=OFF' - job_name: 'CM VS2017, Debug, x64, Schannel, Shared' APPVEYOR_BUILD_WORKER_IMAGE: 'Visual Studio 2017' - CMAKE_VERSION: 3.20.6 + CMAKE_VERSION: 3.20.6 # manually bumped CMAKE_SHA256: f240a38c964712aac474644b3ba21bdc2b4e8d5e31179f67bd2e6f45fa349419 CMAKE_GENERATOR: 'Visual Studio 15 2017' CMAKE_GENERATE: '-A x64' @@ -90,7 +90,7 @@ environment: - job_name: 'CM VS2026, Debug, x64, OpenSSL 3.6 + Schannel, Static, Unicode, Build-tests & examples, clang-cl' APPVEYOR_BUILD_WORKER_IMAGE: 'Visual Studio 2026' - CMAKE_VERSION: 4.2.1 + CMAKE_VERSION: 4.2.1 # manually bumped (first minor version to support VS2026) CMAKE_SHA256: dfc2b2afac257555e3b9ce375b12b2883964283a366c17fec96cf4d17e4f1677 CMAKE_GENERATOR: 'Visual Studio 18 2026' CMAKE_GENERATE: '-A x64 -T ClangCl -DBUILD_SHARED_LIBS=OFF -DCURL_USE_OPENSSL=ON -DENABLE_UNICODE=ON' From 1cd267139e2fb2066ec8f2995de5d6ddd81c4363 Mon Sep 17 00:00:00 2001 From: Stefan Eissing Date: Mon, 5 Oct 2026 12:36:10 +0200 Subject: [PATCH 396/481] ngtcp2: use stream in userdata Change ngtcp2 stream userdata from the transfer to the h3_stream_ctx and lookup the transfer at the multi's table. This replaces hash lookups with table lookups which are faster. Closes #23267 --- lib/vquic/cf-ngtcp2-cmn.c | 68 +++++++++++++++++++++++++------------ lib/vquic/cf-ngtcp2-cmn.h | 9 ++++- lib/vquic/cf-ngtcp2-proxy.c | 10 ++---- lib/vquic/cf-ngtcp2.c | 63 ++++++++++++++++------------------ 4 files changed, 86 insertions(+), 64 deletions(-) diff --git a/lib/vquic/cf-ngtcp2-cmn.c b/lib/vquic/cf-ngtcp2-cmn.c index 9f59011b53cc..4dc4ffa6e990 100644 --- a/lib/vquic/cf-ngtcp2-cmn.c +++ b/lib/vquic/cf-ngtcp2-cmn.c @@ -153,6 +153,29 @@ void Curl_cf_ngtcp2_ctx_cleanup(struct cf_ngtcp2_ctx *ctx) } } +struct Curl_easy *Curl_cf_ngtcp2_get_xfer(struct Curl_cfilter *cf, + void *stream_user_data, + struct h3_stream_ctx **pstream) +{ + struct h3_stream_ctx *stream = stream_user_data; + + if(pstream) + *pstream = stream; + if(stream && (stream->mid != UINT32_MAX)) { + struct Curl_easy *data, *call_data = CF_DATA_CURRENT(cf); + if(!call_data || !call_data->multi) { + DEBUGASSERT(0); + return NULL; + } + data = Curl_multi_get_easy(call_data->multi, stream->mid); + if(data && (data->id == stream->xfer_id)) + return data; + curl_mfprintf(stderr, "H3 stream xfer is NULL\n"); + DEBUGASSERT(0); + } + return NULL; +} + static ngtcp2_conn *get_conn(ngtcp2_crypto_conn_ref *conn_ref) { struct Curl_cfilter *cf = conn_ref->user_data; @@ -323,10 +346,11 @@ static int cb_recv_stream_data(ngtcp2_conn *tconn, uint32_t flags, nghttp3_ssize rc; uint64_t nconsumed; int fin = (flags & NGTCP2_STREAM_DATA_FLAG_FIN) ? 1 : 0; - struct Curl_easy *data = stream_user_data; - struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); - (void)offset; + struct h3_stream_ctx *stream; + struct Curl_easy *data = + Curl_cf_ngtcp2_get_xfer(cf, stream_user_data, &stream); + (void)offset; rc = nghttp3_conn_read_stream(ctx->h3conn, stream_id, buf, buflen, fin); if(rc < 0) { if(data && stream) { @@ -378,7 +402,7 @@ static int cb_stream_close(ngtcp2_conn *tconn, uint32_t flags, { struct Curl_cfilter *cf = user_data; struct cf_ngtcp2_ctx *ctx = cf->ctx; - struct Curl_easy *data = stream_user_data; + struct Curl_easy *data = Curl_cf_ngtcp2_get_xfer(cf, stream_user_data, NULL); int rv; (void)tconn; @@ -412,7 +436,7 @@ static int cb_stream_close2(ngtcp2_conn *tconn, uint32_t flags, { struct Curl_cfilter *cf = user_data; struct cf_ngtcp2_ctx *ctx = cf->ctx; - struct Curl_easy *data = stream_user_data; + struct Curl_easy *data = Curl_cf_ngtcp2_get_xfer(cf, stream_user_data, NULL); uint64_t h3_app_error_code = NGHTTP3_H3_NO_ERROR; int rv; @@ -445,14 +469,15 @@ static int cb_stream_reset(ngtcp2_conn *tconn, int64_t stream_id, { struct Curl_cfilter *cf = user_data; struct cf_ngtcp2_ctx *ctx = cf->ctx; - struct Curl_easy *data = stream_user_data; + struct Curl_easy *data = Curl_cf_ngtcp2_get_xfer(cf, stream_user_data, NULL); int rv; (void)tconn; (void)final_size; (void)app_error_code; rv = nghttp3_conn_shutdown_stream_read(ctx->h3conn, stream_id); - CURL_TRC_CF(data, cf, "[%" PRId64 "] reset -> %d", stream_id, rv); + if(data) + CURL_TRC_CF(data, cf, "[%" PRId64 "] reset -> %d", stream_id, rv); if(rv && rv != NGHTTP3_ERR_STREAM_NOT_FOUND) { return NGTCP2_ERR_CALLBACK_FAILURE; } @@ -501,8 +526,9 @@ static int cb_extend_max_stream_data(ngtcp2_conn *tconn, int64_t stream_id, { struct Curl_cfilter *cf = user_data; struct cf_ngtcp2_ctx *ctx = cf->ctx; - struct Curl_easy *s_data = stream_user_data; struct h3_stream_ctx *stream; + struct Curl_easy *data = + Curl_cf_ngtcp2_get_xfer(cf, stream_user_data, &stream); int rv; (void)tconn; (void)max_data; @@ -511,11 +537,11 @@ static int cb_extend_max_stream_data(ngtcp2_conn *tconn, int64_t stream_id, if(rv && rv != NGHTTP3_ERR_STREAM_NOT_FOUND) { return NGTCP2_ERR_CALLBACK_FAILURE; } - stream = H3_STREAM_CTX(ctx, s_data); if(stream && stream->quic_flow_blocked) { - CURL_TRC_CF(s_data, cf, "[%" PRId64 "] unblock quic flow", stream_id); + CURL_TRC_CF(data, cf, "[%" PRId64 "] unblock quic flow", stream_id); stream->quic_flow_blocked = FALSE; - Curl_multi_mark_dirty(s_data); + if(data) + Curl_multi_mark_dirty(data); } return 0; } @@ -1408,14 +1434,7 @@ static struct h3_stream_ctx *cf_ngtcp2_get_stream(struct cf_ngtcp2_ctx *ctx, static struct h3_stream_ctx *cf_ngtcp2_get_stream(struct cf_ngtcp2_ctx *ctx, int64_t stream_id) { - struct Curl_easy *data = - ngtcp2_conn_get_stream_user_data(ctx->qconn, stream_id); - - if(!data) { - return NULL; - } - - return H3_STREAM_CTX(ctx, data); + return ngtcp2_conn_get_stream_user_data(ctx->qconn, stream_id); } #endif @@ -1875,22 +1894,28 @@ static void cf_ngtcp2_setup_keep_alive(struct Curl_cfilter *cf, } CURLcode Curl_cf_ngtcp2_h3_stream_setup(struct Curl_cfilter *cf, - struct Curl_easy *data) + struct Curl_easy *data, + struct h3_stream_ctx **pstream) { struct cf_ngtcp2_ctx *ctx = cf->ctx; struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); + *pstream = NULL; if(!data) return CURLE_FAILED_INIT; - if(stream) + if(stream) { + *pstream = stream; return CURLE_OK; + } stream = curlx_calloc(1, sizeof(*stream)); if(!stream) return CURLE_OUT_OF_MEMORY; stream->id = -1; + stream->xfer_id = data->id; + stream->mid = data->mid; stream->rx_offset = 0; stream->rx_offset_max = H3_STREAM_WINDOW_SIZE_INITIAL; stream->tx_in_flight_ideal = H3_STREAM_SEND_BUF_INITIAL; @@ -1909,6 +1934,7 @@ CURLcode Curl_cf_ngtcp2_h3_stream_setup(struct Curl_cfilter *cf, if(Curl_u32_ptrset_count(&ctx->streams) == 1) cf_ngtcp2_setup_keep_alive(cf, data); + *pstream = stream; return CURLE_OK; } diff --git a/lib/vquic/cf-ngtcp2-cmn.h b/lib/vquic/cf-ngtcp2-cmn.h index 9f1bd7280c94..7d182ac0e696 100644 --- a/lib/vquic/cf-ngtcp2-cmn.h +++ b/lib/vquic/cf-ngtcp2-cmn.h @@ -165,6 +165,7 @@ void Curl_cf_ngtcp2_cmn_err_set(struct Curl_cfilter *cf, */ struct h3_stream_ctx { int64_t id; /* HTTP/3 stream identifier */ + curl_off_t xfer_id; /* `id` of transfer owning this stream */ struct bufq sendbuf; /* h3 request body */ struct h1_req_parser h1; /* h1 request parsing */ uint64_t error3; /* HTTP/3 stream error code */ @@ -174,6 +175,7 @@ struct h3_stream_ctx { uint64_t rx_offset_max; /* allowed receive offset */ size_t tx_in_flight_size; /* sendbuf data "in flight" */ size_t tx_in_flight_ideal; /* ideal amount of un-acked send data */ + uint32_t mid; /* `mid` of transfer owning this stream */ int status_code; /* HTTP status code */ CURLcode xfer_result; /* result from xfer_resp_write(_hd) */ BIT(resp_hds_complete); /* we have a complete, final response */ @@ -227,7 +229,8 @@ CURLcode Curl_cf_ngtcp2_cmn_set_expiry(struct Curl_cfilter *cf, struct cf_ngtcp2_io_ctx *pktx); CURLcode Curl_cf_ngtcp2_h3_stream_setup(struct Curl_cfilter *cf, - struct Curl_easy *data); + struct Curl_easy *data, + struct h3_stream_ctx **pstream); void Curl_cf_ngtcp2_h3_stream_close(struct Curl_cfilter *cf, struct Curl_easy *data, struct h3_stream_ctx *stream); @@ -246,6 +249,10 @@ CURLcode Curl_cf_ngtcp2_cmn_cntrl(struct Curl_cfilter *cf, struct Curl_easy *data, int event, int arg1, void *arg2); +struct Curl_easy *Curl_cf_ngtcp2_get_xfer(struct Curl_cfilter *cf, + void *stream_user_data, + struct h3_stream_ctx **pstream); + #endif /* !CURL_DISABLE_HTTP && USE_NGTCP2 && USE_NGHTTP3 */ #endif /* HEADER_CURL_VQUIC_CF_NGTCP2_CMN_H */ diff --git a/lib/vquic/cf-ngtcp2-proxy.c b/lib/vquic/cf-ngtcp2-proxy.c index fad4c014571c..1510436cf309 100644 --- a/lib/vquic/cf-ngtcp2-proxy.c +++ b/lib/vquic/cf-ngtcp2-proxy.c @@ -903,15 +903,9 @@ static CURLcode cf_h3_proxy_submit(struct Curl_cfilter *cf, if(result) goto out; - result = Curl_cf_ngtcp2_h3_stream_setup(cf, data); + result = Curl_cf_ngtcp2_h3_stream_setup(cf, data, &stream); if(result) goto out; - stream = H3_STREAM_CTX(ctx, data); - DEBUGASSERT(stream); - if(!stream) { - result = CURLE_FAILED_INIT; - goto out; - } nheader = Curl_dynhds_count(&h2_headers); nva = curlx_malloc(sizeof(nghttp3_nv) * nheader); @@ -955,7 +949,7 @@ static CURLcode cf_h3_proxy_submit(struct Curl_cfilter *cf, preader = &reader; rc = nghttp3_conn_submit_request(ctx->h3conn, stream->id, - nva, nheader, preader, data); + nva, nheader, preader, stream); if(rc) { switch(rc) { diff --git a/lib/vquic/cf-ngtcp2.c b/lib/vquic/cf-ngtcp2.c index bd026e24dd47..d347d52e50d8 100644 --- a/lib/vquic/cf-ngtcp2.c +++ b/lib/vquic/cf-ngtcp2.c @@ -95,14 +95,14 @@ static int cb_h3_stream_close(nghttp3_conn *conn, int64_t stream_id, void *stream_user_data) { struct Curl_cfilter *cf = user_data; - struct cf_ngtcp2_ctx *ctx = cf->ctx; - struct Curl_easy *data = stream_user_data; - struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); + struct h3_stream_ctx *stream; + struct Curl_easy *data = + Curl_cf_ngtcp2_get_xfer(cf, stream_user_data, &stream); (void)conn; (void)stream_id; /* we might be called by nghttp3 after we already cleaned up */ - if(!stream) + if(!data || !stream) return 0; stream->closed = TRUE; @@ -212,13 +212,14 @@ static int cb_h3_recv_data(nghttp3_conn *conn, int64_t stream3_id, { struct Curl_cfilter *cf = user_data; struct cf_ngtcp2_ctx *ctx = cf->ctx; - struct Curl_easy *data = stream_user_data; - struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); + struct h3_stream_ctx *stream; + struct Curl_easy *data = + Curl_cf_ngtcp2_get_xfer(cf, stream_user_data, &stream); (void)conn; (void)stream3_id; - if(!stream) + if(!stream || !data) return NGHTTP3_ERR_CALLBACK_FAILURE; h3_xfer_write_resp(cf, data, stream, (const char *)buf, buflen, FALSE); @@ -240,14 +241,14 @@ static int cb_h3_deferred_consume(nghttp3_conn *conn, int64_t stream3_id, { struct Curl_cfilter *cf = user_data; struct cf_ngtcp2_ctx *ctx = cf->ctx; - struct Curl_easy *data = stream_user_data; - struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); + struct h3_stream_ctx *stream = stream_user_data; (void)conn; /* nghttp3 has consumed bytes on the QUIC stream and we need to * tell the QUIC connection to increase its flow control */ ngtcp2_conn_extend_max_stream_offset(ctx->qconn, stream3_id, consumed); ngtcp2_conn_extend_max_offset(ctx->qconn, consumed); + if(stream) { stream->rx_offset += consumed; stream->rx_offset_max += consumed; @@ -259,15 +260,15 @@ static int cb_h3_end_headers(nghttp3_conn *conn, int64_t stream_id, int fin, void *user_data, void *stream_user_data) { struct Curl_cfilter *cf = user_data; - struct cf_ngtcp2_ctx *ctx = cf->ctx; - struct Curl_easy *data = stream_user_data; - struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); + struct h3_stream_ctx *stream; + struct Curl_easy *data = + Curl_cf_ngtcp2_get_xfer(cf, stream_user_data, &stream); (void)conn; (void)stream_id; (void)fin; (void)cf; - if(!stream) + if(!stream || !data) return 0; /* add a CRLF only if we have received some headers */ h3_xfer_write_resp_hd(cf, data, stream, STRCONST("\r\n"), @@ -291,8 +292,9 @@ static int cb_h3_recv_header(nghttp3_conn *conn, int64_t stream_id, struct cf_ngtcp2_ctx *ctx = cf->ctx; nghttp3_vec h3name = nghttp3_rcbuf_get_buf(name); nghttp3_vec h3val = nghttp3_rcbuf_get_buf(value); - struct Curl_easy *data = stream_user_data; - struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); + struct h3_stream_ctx *stream; + struct Curl_easy *data = + Curl_cf_ngtcp2_get_xfer(cf, stream_user_data, &stream); CURLcode result = CURLE_OK; (void)conn; (void)stream_id; @@ -301,7 +303,7 @@ static int cb_h3_recv_header(nghttp3_conn *conn, int64_t stream_id, (void)cf; /* we might have cleaned up this transfer already */ - if(!stream) + if(!stream || !data) return 0; if(token == NGHTTP3_QPACK_TOKEN__STATUS) { @@ -373,13 +375,14 @@ static int cb_h3_reset_stream(nghttp3_conn *conn, int64_t stream_id, { struct Curl_cfilter *cf = user_data; struct cf_ngtcp2_ctx *ctx = cf->ctx; - struct Curl_easy *data = stream_user_data; + struct Curl_easy *data = Curl_cf_ngtcp2_get_xfer(cf, stream_user_data, NULL); int rv; (void)conn; rv = ngtcp2_conn_shutdown_stream_write(ctx->qconn, 0, stream_id, app_error_code); - CURL_TRC_CF(data, cf, "[%" PRId64 "] reset -> %d", stream_id, rv); + if(data) + CURL_TRC_CF(data, cf, "[%" PRId64 "] reset -> %d", stream_id, rv); if(rv && rv != NGTCP2_ERR_STREAM_NOT_FOUND) { return NGHTTP3_ERR_CALLBACK_FAILURE; } @@ -567,9 +570,7 @@ static int cb_h3_acked_req_body(nghttp3_conn *conn, int64_t stream_id, void *stream_user_data) { struct Curl_cfilter *cf = user_data; - struct cf_ngtcp2_ctx *ctx = cf->ctx; - struct Curl_easy *data = stream_user_data; - struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); + struct h3_stream_ctx *stream = stream_user_data; size_t skiplen; (void)cf; @@ -601,9 +602,9 @@ static nghttp3_ssize cb_h3_read_req_body(nghttp3_conn *conn, int64_t stream_id, void *stream_user_data) { struct Curl_cfilter *cf = user_data; - struct cf_ngtcp2_ctx *ctx = cf->ctx; - struct Curl_easy *data = stream_user_data; - struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); + struct h3_stream_ctx *stream; + struct Curl_easy *data = + Curl_cf_ngtcp2_get_xfer(cf, stream_user_data, &stream); size_t nwritten = 0; size_t nvecs = 0; (void)cf; @@ -612,7 +613,7 @@ static nghttp3_ssize cb_h3_read_req_body(nghttp3_conn *conn, int64_t stream_id, (void)user_data; (void)veccnt; - if(!stream) + if(!stream || !data) return NGHTTP3_ERR_CALLBACK_FAILURE; /* nghttp3 keeps references to the sendbuf data until it is ACKed * by the server (see `cb_h3_acked_req_body()` for updates). @@ -679,15 +680,9 @@ static CURLcode h3_stream_open(struct Curl_cfilter *cf, *pnwritten = 0; Curl_dynhds_init(&h2_headers, 0, DYN_HTTP_REQUEST); - result = Curl_cf_ngtcp2_h3_stream_setup(cf, data); + result = Curl_cf_ngtcp2_h3_stream_setup(cf, data, &stream); if(result) goto out; - stream = H3_STREAM_CTX(ctx, data); - DEBUGASSERT(stream); - if(!stream) { - result = CURLE_FAILED_INIT; - goto out; - } result = Curl_h1_req_parse_read(&stream->h1, buf, len, NULL, !data->state.http_ignorecustom ? @@ -724,7 +719,7 @@ static CURLcode h3_stream_open(struct Curl_cfilter *cf, nva[i].flags = NGHTTP3_NV_FLAG_NONE; } - rc = ngtcp2_conn_open_bidi_stream(ctx->qconn, &sid, data); + rc = ngtcp2_conn_open_bidi_stream(ctx->qconn, &sid, stream); if(rc) { failf(data, "cannot open bidi streams"); result = CURLE_SEND_ERROR; @@ -758,7 +753,7 @@ static CURLcode h3_stream_open(struct Curl_cfilter *cf, } rc = nghttp3_conn_submit_request(ctx->h3conn, stream->id, - nva, nheader, preader, data); + nva, nheader, preader, stream); if(rc) { switch(rc) { case NGHTTP3_ERR_CONN_CLOSING: From 753555d8f8ad5cd837582f89ad30159ac4b7283d Mon Sep 17 00:00:00 2001 From: Jay Satiro Date: Sun, 4 Oct 2026 03:09:32 -0400 Subject: [PATCH 397/481] retry.md: clarify that the server can override retry delay time - Amend --retry explanation of Retry-After and copy it to --retry-delay. This change clarifies that the server's Retry-After header can override the retry delay by putting it quite bluntly: "That means the server can override the retry delay." It also adds that unexpectedly long retry delays from the server can be handled by setting --retry-max-time. Ref: https://github.com/curl/curl/pull/23245#issuecomment-5974010064 Closes https://github.com/curl/curl/pull/23249 --- docs/cmdline-opts/retry-delay.md | 5 +++++ docs/cmdline-opts/retry.md | 4 +++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/docs/cmdline-opts/retry-delay.md b/docs/cmdline-opts/retry-delay.md index dbc78e00d854..d89ee9f08107 100644 --- a/docs/cmdline-opts/retry-delay.md +++ b/docs/cmdline-opts/retry-delay.md @@ -26,3 +26,8 @@ By default, curl uses an exponentially increasing timeout between retries. Starting in curl 8.16.0, this option accepts a time as decimal number for parts of seconds. The decimal value needs to be provided using a dot (.) as decimal separator - not the local version even if it might be using another separator. + +curl complies with the Retry-After: response header if one was present to know +when to issue the next retry (added in 7.66.0). That means the server can +override the retry delay. To handle server retry delays that may be longer +than expected, you can use --retry-max-time to limit the total retry time. diff --git a/docs/cmdline-opts/retry.md b/docs/cmdline-opts/retry.md index 4d9e83cd5f40..ac9166ccf559 100644 --- a/docs/cmdline-opts/retry.md +++ b/docs/cmdline-opts/retry.md @@ -31,4 +31,6 @@ algorithm. See also --retry-max-time to limit the total time allowed for retries. curl complies with the Retry-After: response header if one was present to know -when to issue the next retry (added in 7.66.0). +when to issue the next retry (added in 7.66.0). That means the server can +override the retry delay. To handle server retry delays that may be longer +than expected, you can use --retry-max-time to limit the total retry time. From 6d6bab9c54bdd2c686489b1d105eff4d3d45e466 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Mon, 5 Oct 2026 09:22:47 +0200 Subject: [PATCH 398/481] schannel: handle empty tokens during renegotiation Renegotiation can enter handshake step 2 without buffered input, causing a zero-size allocation that trips curl's debug allocator assertion. Allow this valid empty-token case without changing the logical SSPI length. Bug: https://github.com/git-for-windows/git/issues/6457 Reported-by: jared-m-cohen-civ@users.noreply.github.com Ref: https://github.com/git-for-windows/MINGW-packages/pull/226 Assisted-by: GPT-6.1 Sol Signed-off-by: Johannes Schindelin Closes https://github.com/curl/curl/pull/23272 --- lib/vtls/schannel.c | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/lib/vtls/schannel.c b/lib/vtls/schannel.c index c95e373aabb4..312d86ea3152 100644 --- a/lib/vtls/schannel.c +++ b/lib/vtls/schannel.c @@ -1320,7 +1320,8 @@ static CURLcode schannel_connect_step2(struct Curl_cfilter *cf, /* setup input buffers */ InitSecBuffer(&inbuf[0], SECBUFFER_TOKEN, - curlx_malloc(backend->encdata.offset), + curlx_memdup0((const char *)backend->encdata.buffer, + backend->encdata.offset), curlx_uztoul(backend->encdata.offset)); InitSecBuffer(&inbuf[1], SECBUFFER_EMPTY, NULL, 0); InitSecBufferDesc(&inbuf_desc, inbuf, 2); @@ -1336,10 +1337,6 @@ static CURLcode schannel_connect_step2(struct Curl_cfilter *cf, return CURLE_OUT_OF_MEMORY; } - /* copy received handshake data into input buffer */ - memcpy(inbuf[0].pvBuffer, backend->encdata.buffer, - backend->encdata.offset); - /* The socket must be writable (or a poll error occurred) before we call InitializeSecurityContext to continue processing the received TLS records. This is because that function is not idempotent and we do not From 78c5061d2d2a748917bd65a8b15c0504ca821f2c Mon Sep 17 00:00:00 2001 From: Viktor Szakats Date: Mon, 5 Oct 2026 19:14:21 +0200 Subject: [PATCH 399/481] GHA/https3-linux: isolate and mitigate arm64 compiler optimization bugs with openssl and quiche Allow building quiche and OpenSSL H3 jobs on arm64 runner machines, by avoiding compiler optimization bugs that make runtests 2500, 2501, 2502, 2503 fail with valgrind. - build quiche with `CARGO_PROFILE_RELEASE_OPT_LEVEL=0` on arm64. The next optimization level (1) reproduces the failure. Dropping the `--release` option from `cargo build` also works. Seen with quiche 0.30.0, rustc 1.98.1 (48a229cea 2026-09-01). FTR with optimizations disabled runtests runs at the same speed as optimized Intel, and pytests is still noticeably faster than Intel. - build OpenSSL 3/4 with clang. With gcc-15/16 it also works to pass `--debug` to `./Configure`, or to set `CFLAGS=-O2`, when building on/for arm64. Seen with OpenSSL 4.0.3, 3.5.9, 3.6.5, ubuntu-26.04-arm runner, gcc 15.1.0, gcc 16-20260322-1ubuntu1. - switch openssl-prev jobs to arm64. (Identical performance) Refs: https://doc.rust-lang.org/cargo/reference/config.html#command-line-overrides https://doc.rust-lang.org/cargo/reference/config.html#profile https://doc.rust-lang.org/cargo/reference/profiles.html#opt-level https://doc.rust-lang.org/cargo/reference/profiles.html#release Bug: https://github.com/curl/curl/pull/23214#issuecomment-5955447836 Follow-up to 6c7237191cd31774ec8c15f441f00934066c9947 #23214 Fixes #23237 Closes #23275 --- .github/workflows/http3-linux.yml | 35 ++++++++++++++++++------------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/.github/workflows/http3-linux.yml b/.github/workflows/http3-linux.yml index 2d85ab9aebfe..89bedbfd0ca1 100644 --- a/.github/workflows/http3-linux.yml +++ b/.github/workflows/http3-linux.yml @@ -122,18 +122,18 @@ jobs: - name: 'cache openssl' uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 - id: cache1-openssl-http3-no-deprecated + id: cache2-openssl-http3-no-deprecated env: - cache-name: cache1-openssl-http3-no-deprecated + cache-name: cache2-openssl-http3-no-deprecated with: path: ~/pkg/openssl key: ${{ runner.os }}-http3-build-${{ env.cache-name }}-${{ env.OPENSSL_VERSION }}-${{ runner.arch }} - name: 'cache openssl-prev' uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 - id: cache1-openssl-prev-http3 + id: cache2-openssl-prev-http3 env: - cache-name: cache1-openssl-prev-http3 + cache-name: cache2-openssl-prev-http3 with: path: ~/pkg/openssl-prev key: ${{ runner.os }}-http3-build-${{ env.cache-name }}-${{ env.OPENSSL_PREV_VERSION }}-${{ runner.arch }} @@ -210,8 +210,8 @@ jobs: !steps.cache1-nettle.outputs.cache-hit || !steps.cache1-gnutls.outputs.cache-hit || !steps.cache1-libressl.outputs.cache-hit || - !steps.cache1-openssl-http3-no-deprecated.outputs.cache-hit || - !steps.cache1-openssl-prev-http3.outputs.cache-hit || + !steps.cache2-openssl-http3-no-deprecated.outputs.cache-hit || + !steps.cache2-openssl-prev-http3.outputs.cache-hit || !steps.cache1-wolfssl.outputs.cache-hit || !steps.cache1-nghttp3.outputs.cache-hit || !steps.cache1-ngtcp2-boringssl.outputs.cache-hit || @@ -302,24 +302,26 @@ jobs: cmake --install . - name: 'build openssl' - if: ${{ !steps.cache1-openssl-http3-no-deprecated.outputs.cache-hit }} + if: ${{ !steps.cache2-openssl-http3-no-deprecated.outputs.cache-hit }} run: | cd ~ git clone --quiet --depth 1 --branch "openssl-${OPENSSL_VERSION}" https://github.com/openssl/openssl cd openssl - ./config --prefix=/home/runner/pkg/openssl --libdir=lib no-makedepend no-apps no-docs no-tests no-deprecated + # Prefer clang to avoid test 2500-2503 failing valgrind with gcc-15/16 arm64 non-debug OpenSSL 3/4 builds + CC=clang ./config --prefix=/home/runner/pkg/openssl --libdir=lib no-makedepend no-apps no-docs no-tests no-deprecated make make -j1 install_sw - name: 'build openssl-prev' - if: ${{ !steps.cache1-openssl-prev-http3.outputs.cache-hit }} + if: ${{ !steps.cache2-openssl-prev-http3.outputs.cache-hit }} run: | cd ~ curl --disable --fail --silent --show-error --connect-timeout 15 --max-time 120 --retry 6 --retry-connrefused \ --location --proto-redir =https "https://github.com/openssl/openssl/releases/download/openssl-${OPENSSL_PREV_VERSION}/openssl-${OPENSSL_PREV_VERSION}.tar.gz" --output pkg.bin sha256sum pkg.bin && tar -xzf pkg.bin && rm -f pkg.bin cd "openssl-${OPENSSL_PREV_VERSION}" - ./config --prefix=/home/runner/pkg/openssl-prev --libdir=lib no-makedepend no-apps no-docs no-tests + # Prefer clang to avoid test 2500-2503 failing valgrind with gcc-15/16 arm64 non-debug OpenSSL 3/4 builds + CC=clang ./config --prefix=/home/runner/pkg/openssl-prev --libdir=lib no-makedepend no-apps no-docs no-tests make make -j1 install_sw @@ -523,6 +525,7 @@ jobs: -DCMAKE_UNITY_BUILD=ON - name: 'openssl-prev' + image: ubuntu-26.04-arm install_steps: skipall LDFLAGS: -Wl,-rpath,/home/runner/pkg/openssl-prev/lib PKG_CONFIG_PATH: "\ @@ -533,6 +536,7 @@ jobs: --with-openssl=/home/runner/pkg/openssl-prev --with-ngtcp2=/home/runner/pkg/ngtcp2-openssl-prev --enable-ssls-export - name: 'openssl-prev' + image: ubuntu-26.04-arm tflags: '--min=2000' PKG_CONFIG_PATH: "\ /home/runner/pkg/openssl-prev/lib/pkgconfig:\ @@ -652,9 +656,9 @@ jobs: - name: 'cache openssl' uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 - id: cache1-openssl-http3-no-deprecated + id: cache2-openssl-http3-no-deprecated env: - cache-name: cache1-openssl-http3-no-deprecated + cache-name: cache2-openssl-http3-no-deprecated with: path: ~/pkg/openssl key: ${{ runner.os }}-http3-build-${{ env.cache-name }}-${{ env.OPENSSL_VERSION }}-${{ runner.arch }} @@ -663,9 +667,9 @@ jobs: - name: 'cache openssl-prev' if: ${{ contains(matrix.build.name, 'openssl-prev') }} uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 - id: cache1-openssl-prev-http3 + id: cache2-openssl-prev-http3 env: - cache-name: cache1-openssl-prev-http3 + cache-name: cache2-openssl-prev-http3 with: path: ~/pkg/openssl-prev key: ${{ runner.os }}-http3-build-${{ env.cache-name }}-${{ env.OPENSSL_PREV_VERSION }}-${{ runner.arch }} @@ -761,6 +765,9 @@ jobs: cd ~ git clone --quiet --depth 1 --branch "${QUICHE_VERSION}" --recursive https://github.com/cloudflare/quiche cd quiche + if [ "$(uname -m)" = 'aarch64' ]; then + export CARGO_PROFILE_RELEASE_OPT_LEVEL=0 # to avoid test 2500-2503 failing valgrind with arm64 non-debug quiche 0.30.0 builds + fi cargo build --package quiche --release --features ffi,pkg-config-meta,qlog -vv ln -s libquiche.so target/release/libquiche.so.0 cd .. From 45aea425fa62d1e2d324c61b6964a5aa9bb7a631 Mon Sep 17 00:00:00 2001 From: Viktor Szakats Date: Tue, 6 Oct 2026 04:08:48 +0200 Subject: [PATCH 400/481] CI: sort target file information by filename To ease comparing CI logs. Follow-up to 4cf43508e8e60d0d8acef1beecb0f76040609543 #20355 Follow-up to 55f3ba447fe85f94964f5bbe989b2fd6c3719b99 #16001 Closes #23278 --- .github/workflows/http3-linux.yml | 4 ++-- .github/workflows/linux.yml | 4 ++-- .github/workflows/macos.yml | 12 ++++++------ .github/workflows/non-native.yml | 16 ++++++++-------- .github/workflows/windows.yml | 20 ++++++++++---------- appveyor.sh | 4 ++-- 6 files changed, 30 insertions(+), 30 deletions(-) diff --git a/.github/workflows/http3-linux.yml b/.github/workflows/http3-linux.yml index 89bedbfd0ca1..23820b7793d7 100644 --- a/.github/workflows/http3-linux.yml +++ b/.github/workflows/http3-linux.yml @@ -850,8 +850,8 @@ jobs: - name: 'curl -V' run: | - find . -type f \( -name curl -o -name '*.so.*' -o -name '*.a' \) -print0 | xargs -0 file -- - find . -type f \( -name curl -o -name '*.so.*' -o -name '*.a' \) -print0 | xargs -0 stat -c '%10s bytes: %n' -- + find . -type f \( -name curl -o -name '*.so.*' -o -name '*.a' \) -print0 | sort -z | xargs -0 file -- + find . -type f \( -name curl -o -name '*.so.*' -o -name '*.a' \) -print0 | sort -z | xargs -0 stat -c '%10s bytes: %n' -- bld/src/curl --disable -V - name: 'build tests' diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 25ead485364f..776d0661f8be 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -874,8 +874,8 @@ jobs: - name: 'curl -V' run: | - find . -type f \( -name curl -o -name '*.so.*' -o -name '*.a' \) -print0 | xargs -0 file -- - find . -type f \( -name curl -o -name '*.so.*' -o -name '*.a' \) -print0 | xargs -0 stat -c '%10s bytes: %n' -- + find . -type f \( -name curl -o -name '*.so.*' -o -name '*.a' \) -print0 | sort -z | xargs -0 file -- + find . -type f \( -name curl -o -name '*.so.*' -o -name '*.a' \) -print0 | sort -z | xargs -0 stat -c '%10s bytes: %n' -- bld/src/curl --disable -V - name: 'curl install' diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index 18f44015436a..2f6079364cdd 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -191,8 +191,8 @@ jobs: - name: 'curl info' run: | - find . -type f \( -name curl -o -name '*.dylib' -o -name '*.a' \) -print0 | xargs -0 file -- - find . -type f \( -name curl -o -name '*.dylib' -o -name '*.a' \) -print0 | xargs -0 stat -f '%10z bytes: %N' -- + find . -type f \( -name curl -o -name '*.dylib' -o -name '*.a' \) -print0 | sort -z | xargs -0 file -- + find . -type f \( -name curl -o -name '*.dylib' -o -name '*.a' \) -print0 | sort -z | xargs -0 stat -f '%10z bytes: %N' -- - name: 'build tests' if: ${{ matrix.build.generate }} # skip for autotools to save time @@ -522,8 +522,8 @@ jobs: - name: 'curl -V' run: | - find . -type f \( -name curl -o -name '*.dylib' -o -name '*.a' \) -print0 | xargs -0 file -- - find . -type f \( -name curl -o -name '*.dylib' -o -name '*.a' \) -print0 | xargs -0 stat -f '%10z bytes: %N' -- + find . -type f \( -name curl -o -name '*.dylib' -o -name '*.a' \) -print0 | sort -z | xargs -0 file -- + find . -type f \( -name curl -o -name '*.dylib' -o -name '*.a' \) -print0 | sort -z | xargs -0 stat -f '%10z bytes: %N' -- bld/src/curl --disable --version - name: 'curl install' @@ -773,6 +773,6 @@ jobs: - name: 'curl -V' run: | - find . -type f \( -name curl -o -name '*.dylib' -o -name '*.a' \) -print0 | xargs -0 file -- - find . -type f \( -name curl -o -name '*.dylib' -o -name '*.a' \) -print0 | xargs -0 stat -f '%10z bytes: %N' -- + find . -type f \( -name curl -o -name '*.dylib' -o -name '*.a' \) -print0 | sort -z | xargs -0 file -- + find . -type f \( -name curl -o -name '*.dylib' -o -name '*.a' \) -print0 | sort -z | xargs -0 stat -f '%10z bytes: %N' -- bld/src/curl --disable --version diff --git a/.github/workflows/non-native.yml b/.github/workflows/non-native.yml index da2187318038..c79d7dfbe378 100644 --- a/.github/workflows/non-native.yml +++ b/.github/workflows/non-native.yml @@ -212,8 +212,8 @@ jobs: - name: 'curl -V' run: | - find . -type f \( -name curl -o -name '*.so.*' -o -name '*.a' \) -print0 | xargs -0 file -- - find . -type f \( -name curl -o -name '*.so.*' -o -name '*.a' \) -print0 | xargs -0 stat -f '%10z bytes: %N' -- + find . -type f \( -name curl -o -name '*.so.*' -o -name '*.a' \) -print0 | sort -z | xargs -0 file -- + find . -type f \( -name curl -o -name '*.so.*' -o -name '*.a' \) -print0 | sort -z | xargs -0 stat -f '%10z bytes: %N' -- bld/src/curl --disable --version - name: 'curl install' @@ -354,8 +354,8 @@ jobs: - name: 'curl info' run: | - find . -type f \( -name curl -o -name '*.a' \) -print0 | xargs -0 file -- - find . -type f \( -name curl -o -name '*.a' \) -print0 | xargs -0 stat -c '%10s bytes: %n' -- + find . -type f \( -name curl -o -name '*.a' \) -print0 | sort -z | xargs -0 file -- + find . -type f \( -name curl -o -name '*.a' \) -print0 | sort -z | xargs -0 stat -c '%10s bytes: %n' -- - name: 'build tests' if: ${{ matrix.build == 'cmake' }} # skip for autotools to save time @@ -466,8 +466,8 @@ jobs: - name: 'curl info' run: | - find . -type f \( -name curl -o -name '*.so' -o -name '*.a' \) -print0 | xargs -0 file -- - find . -type f \( -name curl -o -name '*.so' -o -name '*.a' \) -print0 | xargs -0 stat -c '%10s bytes: %n' -- + find . -type f \( -name curl -o -name '*.so' -o -name '*.a' \) -print0 | sort -z | xargs -0 file -- + find . -type f \( -name curl -o -name '*.so' -o -name '*.a' \) -print0 | sort -z | xargs -0 stat -c '%10s bytes: %n' -- - name: 'build tests' run: | @@ -586,8 +586,8 @@ jobs: - name: 'curl info' run: | - find . \( -name '*.exe' -o -name '*.a' \) -print0 | xargs -0 file -- - find . \( -name '*.exe' -o -name '*.a' \) -print0 | xargs -0 stat -c '%10s bytes: %n' -- + find . \( -name '*.exe' -o -name '*.a' \) -print0 | sort -z | xargs -0 file -- + find . \( -name '*.exe' -o -name '*.a' \) -print0 | sort -z | xargs -0 stat -c '%10s bytes: %n' -- - name: 'build tests' if: ${{ matrix.build == 'cmake' }} # skip for autotools to save time diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index a4c825df9d39..aa3a0b36cca8 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -183,8 +183,8 @@ jobs: timeout-minutes: 1 run: | PATH=/usr/bin - find . \( -name '*.exe' -o -name '*.dll' -o -name '*.a' \) -print0 | grep -z curl | xargs -0 file -- - find . \( -name '*.exe' -o -name '*.dll' -o -name '*.a' \) -print0 | grep -z curl | xargs -0 stat -c '%10s bytes: %n' -- + find . \( -name '*.exe' -o -name '*.dll' -o -name '*.a' \) -print0 | grep -z curl | sort -z | xargs -0 file -- + find . \( -name '*.exe' -o -name '*.dll' -o -name '*.a' \) -print0 | grep -z curl | sort -z | xargs -0 stat -c '%10s bytes: %n' -- if [ "${MATRIX_BUILD}" = 'cmake' ]; then PATH="$PWD/bld/lib:$PATH" fi @@ -466,8 +466,8 @@ jobs: # avoid libtool's curl.exe wrapper for shared builds mv bld/src/.libs/curl.exe bld/src/curl.exe || true fi - find . \( -name '*.exe' -o -name '*.dll' -o -name '*.a' \) -print0 | grep -z curl | xargs -0 file -- - find . \( -name '*.exe' -o -name '*.dll' -o -name '*.a' \) -print0 | grep -z curl | xargs -0 stat -c '%10s bytes: %n' -- + find . \( -name '*.exe' -o -name '*.dll' -o -name '*.a' \) -print0 | grep -z curl | sort -z | xargs -0 file -- + find . \( -name '*.exe' -o -name '*.dll' -o -name '*.a' \) -print0 | grep -z curl | sort -z | xargs -0 stat -c '%10s bytes: %n' -- if [ "${MATRIX_TEST}" != 'uwp' ]; then # curl: error initializing curl library bld/src/curl.exe --disable --version fi @@ -747,8 +747,8 @@ jobs: - name: 'curl -V' timeout-minutes: 1 run: | - /usr/bin/find . \( -name '*.exe' -o -name '*.dll' -o -name '*.a' \) -print0 | grep -z curl | xargs -0 file -- - /usr/bin/find . \( -name '*.exe' -o -name '*.dll' -o -name '*.a' \) -print0 | grep -z curl | xargs -0 stat -c '%10s bytes: %n' -- + /usr/bin/find . \( -name '*.exe' -o -name '*.dll' -o -name '*.a' \) -print0 | grep -z curl | sort -z | xargs -0 file -- + /usr/bin/find . \( -name '*.exe' -o -name '*.dll' -o -name '*.a' \) -print0 | grep -z curl | sort -z | xargs -0 stat -c '%10s bytes: %n' -- PATH="$PWD/bld/lib:$PATH" bld/src/curl.exe --disable --version @@ -884,8 +884,8 @@ jobs: - name: 'curl info' run: | - find . \( -name '*.exe' -o -name '*.dll' -o -name '*.a' \) -print0 | grep -z curl | xargs -0 file -- - find . \( -name '*.exe' -o -name '*.dll' -o -name '*.a' \) -print0 | grep -z curl | xargs -0 stat -c '%10s bytes: %n' -- + find . \( -name '*.exe' -o -name '*.dll' -o -name '*.a' \) -print0 | grep -z curl | sort -z | xargs -0 file -- + find . \( -name '*.exe' -o -name '*.dll' -o -name '*.a' \) -print0 | grep -z curl | sort -z | xargs -0 stat -c '%10s bytes: %n' -- - name: 'build tests' if: ${{ matrix.build == 'cmake' }} # Save time by skipping this for autotools and clang-tidy @@ -1157,8 +1157,8 @@ jobs: - name: 'curl -V' timeout-minutes: 1 run: | - /usr/bin/find . \( -name '*.exe' -o -name '*.dll' -o -name '*.lib' -o -name '*.pdb' \) -print0 | grep -z curl | xargs -0 file -- - /usr/bin/find . \( -name '*.exe' -o -name '*.dll' -o -name '*.lib' -o -name '*.pdb' \) -print0 | grep -z curl | xargs -0 stat -c '%10s bytes: %n' -- + /usr/bin/find . \( -name '*.exe' -o -name '*.dll' -o -name '*.lib' -o -name '*.pdb' \) -print0 | grep -z curl | sort -z | xargs -0 file -- + /usr/bin/find . \( -name '*.exe' -o -name '*.dll' -o -name '*.lib' -o -name '*.pdb' \) -print0 | grep -z curl | sort -z | xargs -0 stat -c '%10s bytes: %n' -- if [ "${MATRIX_PLAT}" != 'uwp' ]; then # Missing: ucrtbased.dll, VCRUNTIME140D.dll, VCRUNTIME140D_APP.dll PATH="$PWD/bld/lib/${MATRIX_TYPE}:$(cygpath --unix "${VCPKG_INSTALLATION_ROOT}")/installed/${MATRIX_ARCH}-${MATRIX_PLAT}/${MATRIX_TYPE}/bin:$PATH" "bld/src/${MATRIX_TYPE}/curl.exe" --disable --version diff --git a/appveyor.sh b/appveyor.sh index 52f1252c4a59..779553a38d3e 100644 --- a/appveyor.sh +++ b/appveyor.sh @@ -105,8 +105,8 @@ else curl="build/${platdir}/${VC_VERSION}/${PRJ_CFG}/curl${binsuffix}.exe" fi -find . \( -name '*.exe' -o -name '*.dll' -o -name '*.lib' -o -name '*.pdb' \) -print0 | grep -z curl | xargs -0 file -- -find . \( -name '*.exe' -o -name '*.dll' -o -name '*.lib' -o -name '*.pdb' \) -print0 | grep -z curl | xargs -0 stat -c '%10s bytes: %n' -- +find . \( -name '*.exe' -o -name '*.dll' -o -name '*.lib' -o -name '*.pdb' \) -print0 | grep -z curl | sort -z | xargs -0 file -- +find . \( -name '*.exe' -o -name '*.dll' -o -name '*.lib' -o -name '*.pdb' \) -print0 | grep -z curl | sort -z | xargs -0 stat -c '%10s bytes: %n' -- if [ -z "${SKIP_RUN:-}" ]; then "${curl}" --disable --version From fe880e5479367f3c6f9ffb991f3d5e78577f00ab Mon Sep 17 00:00:00 2001 From: Daniel Stenberg Date: Tue, 6 Oct 2026 09:32:35 +0200 Subject: [PATCH 401/481] cookie: cookies for a PSL domain from jar just limits tailmatch MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We can set cookies for the PSL itself, just not tailmatch. This also works fine with the particular quirk that psl_is_public_suffix() returns true even for dotless domains such as "my-machine". Verified by test 2582 Follow-up to 95c1e8915dce64606bd753fd4 This regression shipped in 8.22.0 Reported-by: Rémi NICOLE Fixes #23261 Closes #23281 --- lib/cookie.c | 11 ++++----- tests/data/Makefile.am | 2 +- tests/data/test2582 | 54 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 60 insertions(+), 7 deletions(-) create mode 100644 tests/data/test2582 diff --git a/lib/cookie.c b/lib/cookie.c index ccfa58a07d02..4aedd728e180 100644 --- a/lib/cookie.c +++ b/lib/cookie.c @@ -835,12 +835,11 @@ static bool is_public_suffix(struct Curl_easy *data, co->tailmatch = FALSE; } else { - /* libpsl says localhost is a PSL, we think not */ - acceptable = - curl_strequal(lcookie, "localhost") || - /* note that this PSL function returns the opposite value than - psl_is_cookie_domain_acceptable() does */ - !psl_is_public_suffix(psl, lcookie); + bool suffix = psl_is_public_suffix(psl, lcookie); + acceptable = TRUE; + if(suffix) + /* switch off tailmatching when set for a PSL domain */ + co->tailmatch = FALSE; } Curl_psl_release(data); } diff --git a/tests/data/Makefile.am b/tests/data/Makefile.am index a76a24aeb139..c5469f6d96bb 100644 --- a/tests/data/Makefile.am +++ b/tests/data/Makefile.am @@ -1997,7 +1997,7 @@ TESTCASES = \ test2504 \ test2505 \ test2506 \ - \ + test2582 \ test2600 \ test2601 \ test2602 \ diff --git a/tests/data/test2582 b/tests/data/test2582 new file mode 100644 index 000000000000..614bb187d6ba --- /dev/null +++ b/tests/data/test2582 @@ -0,0 +1,54 @@ + + + + +cookies + + + + + +HTTP/1.1 200 OK +Content-Length: 0 + + + + + +# Client-side + + +PSL +cookies + + +http + + +Get cookies from jar using single-component domain name + + +--resolve my-machine:%HTTPPORT:%HOSTIP -b "%LOGDIR/dump%TESTNUMBER" http://my-machine:%HTTPPORT/%TESTNUMBER + + + +# Netscape HTTP Cookie File +# https://curl.se/docs/http-cookies.html +# This file was generated by libcurl! Edit at your own risk. + +my-machine%TABFALSE%TAB/%TABFALSE%TAB0%TABworld%TABcrazy + + + +# Verify data after the test has been "shot" + + +GET /%TESTNUMBER HTTP/1.1 +Host: my-machine:%HTTPPORT +User-Agent: curl/%VERSION +Accept: */* +Cookie: world=crazy + + + + From 4ff20ec7fb4006d690c06e91399e36f9f37f8739 Mon Sep 17 00:00:00 2001 From: Daniel Stenberg Date: Tue, 6 Oct 2026 08:44:15 +0200 Subject: [PATCH 402/481] DEPRECATE: Common Name support in TLS certificates for OpenSSL We remove support after 8.23.0 Closes #23280 --- docs/DEPRECATE.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/docs/DEPRECATE.md b/docs/DEPRECATE.md index 27e1052e8056..8774dd79db5c 100644 --- a/docs/DEPRECATE.md +++ b/docs/DEPRECATE.md @@ -51,6 +51,18 @@ We estimate that barely any libcurl users still use this feature. HTTP/2 Server Push gets removed in March 2027. +## Common Name support in TLS certificates for OpenSSL + +The Common Name, (`CN`) field is deprecated since RFC 2818. It was downgraded +to a SHOULD in RFC 6125 and finally removed in RFC 9525. Browsers have not +supported this field since 2012. + +In the release after 8.23.0 curl removes support for this field in code we +control - which primarily means in the OpenSSL (and forks) backend. The other +TLS libraries provide their own functions for verification. + +Pending PR: https://github.com/curl/curl/pull/22845 + ## Past removals - axTLS (removed in 7.63.0) From e164492f3cfdd5d5069cb659aeaedb6370651fee Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 6 Oct 2026 04:21:22 +0000 Subject: [PATCH 403/481] GHA: update debian:bookworm-slim Docker digest to 7c7b2c9 Closes #23279 --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index df7b206dfaf9..e52e1b5ca365 100644 --- a/Dockerfile +++ b/Dockerfile @@ -24,7 +24,7 @@ # $ ./scripts/maketgz 8.7.1 # To update, get the latest digest e.g. from https://hub.docker.com/_/debian/tags -FROM debian:bookworm-slim@sha256:3783cc01769c7b2b1b83a5c5ad96c815348e28ed7da68e2e3687004faa906251 +FROM debian:bookworm-slim@sha256:7c7b2c966bc9ee8cedfeef67e0e279108992c77681fa595db4a9d65c06ccc587 RUN apt-get update -qq && apt-get install -qq -y --no-install-recommends \ build-essential make autoconf automake libtool git perl zip zlib1g-dev gawk && \ From 42376a7d314b7d0a55a1b8ceb18d61d1e00b466e Mon Sep 17 00:00:00 2001 From: Viktor Szakats Date: Mon, 5 Oct 2026 09:21:14 +0200 Subject: [PATCH 404/481] pytest: set `PerSourcePenalties no` when sshd supports it Also: - limit sshd version detection to the `major.minor` value, ignore `p*` (and similar) suffixes. - show sshd version on pytest startup. Follow-up to e351db113dc031d55700dea0da87c90380e9643d #23253 Closes #23259 --- tests/http/conftest.py | 2 ++ tests/http/testenv/env.py | 10 +++++++++- tests/http/testenv/sshd.py | 2 ++ 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/tests/http/conftest.py b/tests/http/conftest.py index 31a3d55b6487..21f633785e7b 100644 --- a/tests/http/conftest.py +++ b/tests/http/conftest.py @@ -57,6 +57,8 @@ def pytest_report_header(config): report.extend([f" h2o: {env.h2o_version()}"]) if env.has_caddy(): report.extend([f" Caddy: {env.caddy_version()}"]) + if env.has_sshd(): + report.extend([f" sshd: {env.sshd_version()}"]) if env.has_vsftpd(): report.extend([f" VsFTPD: {env.vsftpd_version()}"]) buildinfo_fn = os.path.join(env.build_dir, "buildinfo.txt") diff --git a/tests/http/testenv/env.py b/tests/http/testenv/env.py index 4cc1696de221..63d48c9de35d 100644 --- a/tests/http/testenv/env.py +++ b/tests/http/testenv/env.py @@ -352,7 +352,7 @@ def __init__( if p.returncode != 0: self.sshd = None else: - m = re.match(r"^OpenSSH_(\d+\.\d+.*),.*", p.stderr) + m = re.match(r"^OpenSSH_(\d+\.\d+).*,.*", p.stderr) assert m, f"version: {p.stderr}" if m: self._sshd_version = m.group(1) @@ -439,6 +439,10 @@ def vsftpd_version(self): def h2o_version(self): return self._h2o_version + @property + def sshd_version(self): + return self._sshd_version + @property def tcpdump(self) -> Optional[str]: return self._tcpdump @@ -658,6 +662,10 @@ def caddy_version(cls) -> str: def h2o_version(cls) -> str: return cls.CONFIG.h2o_version + @classmethod + def sshd_version(cls) -> str: + return cls.CONFIG.sshd_version + @classmethod def caddy_is_at_least(cls, minv) -> bool: return cls.CONFIG.caddy_is_at_least(minv) diff --git a/tests/http/testenv/sshd.py b/tests/http/testenv/sshd.py index 7753e855c9f1..24922bef694f 100644 --- a/tests/http/testenv/sshd.py +++ b/tests/http/testenv/sshd.py @@ -277,6 +277,8 @@ def _write_config(self): f'AuthorizedKeysFile {self._auth_keys}', f'PidFile {self._pid_file}', ] + if Env.CONFIG.versiontuple(self.env.sshd_version()) >= Env.CONFIG.versiontuple('9.8'): + conf.append('PerSourcePenalties no') conf.extend([f'HostKey {key_file}' for key_file in self._host_key_files]) if self._sftpd: conf.append(f'Subsystem sftp {self._sftpd}') From ed9a5f9d0e7f87bd614e4c82a3812e85e17ce971 Mon Sep 17 00:00:00 2001 From: Stefan Eissing Date: Fri, 2 Oct 2026 13:26:58 +0200 Subject: [PATCH 405/481] cshutdown: use ptrarray instead of llist Replace the llist used so far in connection shutdown handling with a new `ptrarray`, removing the llist nodes from `struct connectdata`. Add unit tests of `ptrarray` in unit3211. Closes #23234 --- lib/Makefile.inc | 2 + lib/cshutdn.c | 116 +++++++++++++++++++++--------------------- lib/cshutdn.h | 4 +- lib/ptrarray.c | 98 +++++++++++++++++++++++++++++++++++ lib/ptrarray.h | 50 ++++++++++++++++++ lib/urldata.h | 1 - tests/data/test3211 | 3 +- tests/unit/unit3211.c | 41 +++++++++++++++ 8 files changed, 253 insertions(+), 62 deletions(-) create mode 100644 lib/ptrarray.c create mode 100644 lib/ptrarray.h diff --git a/lib/Makefile.inc b/lib/Makefile.inc index 183cf37a40f7..d48a2b977eb2 100644 --- a/lib/Makefile.inc +++ b/lib/Makefile.inc @@ -265,6 +265,7 @@ LIB_CFILES = \ protocol.c \ proxy.c \ psl.c \ + ptrarray.c \ rand.c \ ratelimit.c \ request.c \ @@ -396,6 +397,7 @@ LIB_HFILES = \ protocol.h \ proxy.h \ psl.h \ + ptrarray.h \ rand.h \ ratelimit.h \ request.h \ diff --git a/lib/cshutdn.c b/lib/cshutdn.c index 00b5d0beac4c..a5ca88405282 100644 --- a/lib/cshutdn.c +++ b/lib/cshutdn.c @@ -277,24 +277,24 @@ static size_t cshutdn_destroy_oldest(struct cshutdn *cshutdn, const char *destination, size_t count) { - struct Curl_llist_node *e = Curl_llist_head(&cshutdn->list), *next; struct Curl_sigpipe_ctx sigpipe_ctx; - size_t n = 0; + size_t removed = 0; + uint32_t i = 0; sigpipe_init(&sigpipe_ctx); - while(e && (n < count)) { - struct connectdata *conn = Curl_node_elem(e); - next = Curl_node_next(e); + while((removed < count) && (i < CURL_PTRARRAY_COUNT(&cshutdn->conns))) { + struct connectdata *conn = CURL_PTRARRAY_GET(&cshutdn->conns, i); if(!destination || !strcmp(destination, conn->destination)) { - Curl_node_remove(e); + Curl_ptrarray_remove(&cshutdn->conns, i); sigpipe_apply(admin, &sigpipe_ctx); Curl_cshutdn_terminate(admin, conn, FALSE); - ++n; + ++removed; } - e = next; + else + ++i; } sigpipe_restore(&sigpipe_ctx); - return n; + return removed; } size_t Curl_cshutdn_close_oldest(struct cshutdn *cshutdn, @@ -335,34 +335,32 @@ static void cshutdn_perform(struct cshutdn *cshutdn, struct Curl_easy *admin, struct Curl_sigpipe_ctx *sigpipe_ctx) { - struct Curl_llist_node *e = Curl_llist_head(&cshutdn->list); - struct Curl_llist_node *enext; struct connectdata *conn; timediff_t next_expire_ms = 0, ms; + uint32_t i = 0; bool done; - if(!e) + if(!CURL_PTRARRAY_COUNT(&cshutdn->conns)) return; - CURL_TRC_M(admin, "[SHUTDOWN] perform on %zu connections", - Curl_llist_count(&cshutdn->list)); + CURL_TRC_M(admin, "[SHUTDOWN] perform on %u connections", + CURL_PTRARRAY_COUNT(&cshutdn->conns)); sigpipe_apply(admin, sigpipe_ctx); - while(e) { - enext = Curl_node_next(e); - conn = Curl_node_elem(e); + while(i < CURL_PTRARRAY_COUNT(&cshutdn->conns)) { + conn = CURL_PTRARRAY_GET(&cshutdn->conns, i); Curl_cshutdn_try_once(admin, conn, &done); if(done) { - Curl_node_remove(e); + Curl_ptrarray_remove(&cshutdn->conns, i); Curl_cshutdn_terminate(admin, conn, FALSE); } else { + ++i; /* idata has one timer list, but maybe more than one connection. * Set EXPIRE_SHUTDOWN to the smallest time left for all. */ ms = cshutdn_conn_timeleft(admin, conn); if(ms && (!next_expire_ms || (ms < next_expire_ms))) next_expire_ms = ms; } - e = enext; } if(next_expire_ms) @@ -374,19 +372,18 @@ static void cshutdn_terminate_all(struct cshutdn *cshutdn, int timeout_ms) { struct curltime started = *Curl_pgrs_now(admin); - struct Curl_llist_node *e; struct Curl_sigpipe_ctx sigpipe_ctx; CURL_TRC_M(admin, "[SHUTDOWN] shutdown all"); sigpipe_init(&sigpipe_ctx); - while(Curl_llist_head(&cshutdn->list)) { + while(CURL_PTRARRAY_COUNT(&cshutdn->conns)) { timediff_t spent_ms; int remain_ms; cshutdn_perform(cshutdn, admin, &sigpipe_ctx); - if(!Curl_llist_head(&cshutdn->list)) { + if(!CURL_PTRARRAY_COUNT(&cshutdn->conns)) { CURL_TRC_M(admin, "[SHUTDOWN] shutdown finished cleanly"); break; } @@ -407,21 +404,18 @@ static void cshutdn_terminate_all(struct cshutdn *cshutdn, } /* Terminate any remaining. */ - e = Curl_llist_head(&cshutdn->list); - while(e) { - struct connectdata *conn = Curl_node_elem(e); - Curl_node_remove(e); + while(CURL_PTRARRAY_COUNT(&cshutdn->conns)) { + struct connectdata *conn = Curl_ptrarray_remove(&cshutdn->conns, 0); Curl_cshutdn_terminate(admin, conn, FALSE); - e = Curl_llist_head(&cshutdn->list); } - DEBUGASSERT(!Curl_llist_count(&cshutdn->list)); + DEBUGASSERT(!CURL_PTRARRAY_COUNT(&cshutdn->conns)); sigpipe_restore(&sigpipe_ctx); } void Curl_cshutdn_init(struct cshutdn *cshutdn) { - Curl_llist_init(&cshutdn->list, NULL); + Curl_ptrarray_init(&cshutdn->conns); } void Curl_cshutdn_destroy(struct cshutdn *cshutdn, @@ -440,9 +434,9 @@ void Curl_cshutdn_destroy(struct cshutdn *cshutdn, } } #endif - if(Curl_llist_count(&cshutdn->list)) { - CURL_TRC_M(admin, "[SHUTDOWN] destroy, %zu connections, timeout=%dms", - Curl_llist_count(&cshutdn->list), timeout_ms); + if(CURL_PTRARRAY_COUNT(&cshutdn->conns)) { + CURL_TRC_M(admin, "[SHUTDOWN] destroy, %u connections, timeout=%dms", + CURL_PTRARRAY_COUNT(&cshutdn->conns), timeout_ms); cshutdn_terminate_all(cshutdn, admin, timeout_ms); } } @@ -451,7 +445,7 @@ void Curl_cshutdn_destroy(struct cshutdn *cshutdn, size_t Curl_cshutdn_count(struct cshutdn *cshutdn) { if(cshutdn) { - return Curl_llist_count(&cshutdn->list); + return CURL_PTRARRAY_COUNT(&cshutdn->conns); } return 0; } @@ -461,12 +455,11 @@ size_t Curl_cshutdn_dest_count(struct cshutdn *cshutdn, { if(cshutdn) { size_t n = 0; - struct Curl_llist_node *e = Curl_llist_head(&cshutdn->list); - while(e) { - struct connectdata *conn = Curl_node_elem(e); + uint32_t i = 0; + for(i = 0; i < CURL_PTRARRAY_COUNT(&cshutdn->conns); ++i) { + struct connectdata *conn = CURL_PTRARRAY_GET(&cshutdn->conns, i); if(!strcmp(destination, conn->destination)) ++n; - e = Curl_node_next(e); } return n; } @@ -492,7 +485,7 @@ void Curl_cshutdn_add(struct cshutdn *cshutdn, /* Add the connection to our shutdown list for non-blocking shutdown * during multi processing. */ - if(max_shutdowns <= Curl_llist_count(&cshutdn->list)) { + if(max_shutdowns <= CURL_PTRARRAY_COUNT(&cshutdn->conns)) { CURL_TRC_M(admin, "[SHUTDOWN] discarding oldest shutdown connection " "due to shutdown limit of %zu", max_shutdowns); cshutdn_destroy_oldest(cshutdn, admin, NULL, 1); @@ -505,10 +498,15 @@ void Curl_cshutdn_add(struct cshutdn *cshutdn, return; } - Curl_llist_append(&cshutdn->list, conn, &conn->cshutdn_node); + if(Curl_ptrarray_add(&cshutdn->conns, conn)) { + CURL_TRC_M(admin, "[SHUTDOWN] adding failed, discarding #%" + FMT_OFF_T, conn->connection_id); + Curl_cshutdn_terminate(admin, conn, FALSE); + return; + } CURL_TRC_M(admin, "[SHUTDOWN] added #%" FMT_OFF_T - " to shutdowns, now %zu conns in shutdown", - conn->connection_id, Curl_llist_count(&cshutdn->list)); + " to shutdowns, now %u conns in shutdown", + conn->connection_id, CURL_PTRARRAY_COUNT(&cshutdn->conns)); } void Curl_cshutdn_perform(struct cshutdn *cshutdn, @@ -524,14 +522,14 @@ void Curl_cshutdn_setfds(struct cshutdn *cshutdn, fd_set *read_fd_set, fd_set *write_fd_set, int *maxfd) { - if(Curl_llist_head(&cshutdn->list)) { - struct Curl_llist_node *e; + if(CURL_PTRARRAY_COUNT(&cshutdn->conns)) { struct easy_pollset ps; + uint32_t i; Curl_pollset_init(&ps); - for(e = Curl_llist_head(&cshutdn->list); e; e = Curl_node_next(e)) { - unsigned int i; - struct connectdata *conn = Curl_node_elem(e); + for(i = 0; i < CURL_PTRARRAY_COUNT(&cshutdn->conns); ++i) { + unsigned int j; + struct connectdata *conn = CURL_PTRARRAY_GET(&cshutdn->conns, i); CURLcode result; Curl_pollset_reset(&ps); @@ -542,15 +540,15 @@ void Curl_cshutdn_setfds(struct cshutdn *cshutdn, if(result) continue; - for(i = 0; i < ps.n; i++) { - curl_socket_t sock = ps.sockets[i]; + for(j = 0; j < ps.n; j++) { + curl_socket_t sock = ps.sockets[j]; if(!FDSET_SOCK(sock)) continue; - if(ps.actions[i] & CURL_POLL_IN) + if(ps.actions[j] & CURL_POLL_IN) FD_SET(sock, read_fd_set); - if(ps.actions[i] & CURL_POLL_OUT) + if(ps.actions[j] & CURL_POLL_OUT) FD_SET(sock, write_fd_set); - if((ps.actions[i] & (CURL_POLL_OUT | CURL_POLL_IN)) && + if((ps.actions[j] & (CURL_POLL_OUT | CURL_POLL_IN)) && ((int)sock > *maxfd)) *maxfd = (int)sock; } @@ -566,15 +564,15 @@ unsigned int Curl_cshutdn_add_waitfds(struct cshutdn *cshutdn, { unsigned int need = 0; - if(Curl_llist_head(&cshutdn->list)) { - struct Curl_llist_node *e; + if(CURL_PTRARRAY_COUNT(&cshutdn->conns)) { struct easy_pollset ps; struct connectdata *conn; + uint32_t i; CURLcode result; Curl_pollset_init(&ps); - for(e = Curl_llist_head(&cshutdn->list); e; e = Curl_node_next(e)) { - conn = Curl_node_elem(e); + for(i = 0; i < CURL_PTRARRAY_COUNT(&cshutdn->conns); ++i) { + conn = CURL_PTRARRAY_GET(&cshutdn->conns, i); Curl_pollset_reset(&ps); Curl_attach_connection(admin, conn, FALSE); result = Curl_conn_adjust_pollset(admin, conn, &ps); @@ -594,14 +592,14 @@ CURLcode Curl_cshutdn_add_pollfds(struct cshutdn *cshutdn, { CURLcode result = CURLE_OK; - if(Curl_llist_head(&cshutdn->list)) { - struct Curl_llist_node *e; + if(CURL_PTRARRAY_COUNT(&cshutdn->conns)) { struct easy_pollset ps; struct connectdata *conn; + uint32_t i; Curl_pollset_init(&ps); - for(e = Curl_llist_head(&cshutdn->list); e; e = Curl_node_next(e)) { - conn = Curl_node_elem(e); + for(i = 0; i < CURL_PTRARRAY_COUNT(&cshutdn->conns); ++i) { + conn = CURL_PTRARRAY_GET(&cshutdn->conns, i); Curl_pollset_reset(&ps); Curl_attach_connection(admin, conn, FALSE); result = Curl_conn_adjust_pollset(admin, conn, &ps); diff --git a/lib/cshutdn.h b/lib/cshutdn.h index f5ba3509958c..d42d2cb9f6db 100644 --- a/lib/cshutdn.h +++ b/lib/cshutdn.h @@ -24,6 +24,8 @@ * SPDX-License-Identifier: curl * ***************************************************************************/ +#include "ptrarray.h" + struct connectdata; struct Curl_easy; struct curl_pollfds; @@ -74,7 +76,7 @@ void Curl_cshutdn_try_once(struct Curl_easy *admin, * the connections to be shut down. It registers timers and * sockets to monitor via the multi handle. */ struct cshutdn { - struct Curl_llist list; /* connections being shut down */ + struct ptrarray conns; /* connections being shut down */ }; /* Init as part of the given multi handle. */ diff --git a/lib/ptrarray.c b/lib/ptrarray.c new file mode 100644 index 000000000000..fd10a1c8d68b --- /dev/null +++ b/lib/ptrarray.c @@ -0,0 +1,98 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ +#include "curl_setup.h" + +#include "ptrarray.h" + +#define CURL_PTRARRAY_MAX_GROW (16 * 1024) + +void Curl_ptrarray_init(struct ptrarray *pa) +{ + pa->data = NULL; + pa->n = pa->capacity = 0; +} + +void Curl_ptrarray_clear(struct ptrarray *pa) +{ + curlx_safefree(pa->data); + pa->n = pa->capacity = 0; +} + +uint32_t Curl_ptrarray_count(struct ptrarray *pa) +{ + return pa->n; +} + +CURLcode Curl_ptrarray_add(struct ptrarray *pa, void *ptr) +{ + if(!ptr) + return CURLE_BAD_FUNCTION_ARGUMENT; + if(pa->n >= pa->capacity) { + uint32_t growth; + void **ndata; + + if(pa->capacity == UINT32_MAX) + return CURLE_OUT_OF_MEMORY; + if(!pa->capacity) + growth = 8; + else if((UINT32_MAX / 2) > pa->capacity) + growth = CURLMIN(2 * pa->capacity, CURL_PTRARRAY_MAX_GROW); + else + growth = ((UINT32_MAX - pa->capacity) > CURL_PTRARRAY_MAX_GROW) ? + CURL_PTRARRAY_MAX_GROW : (UINT32_MAX - pa->capacity); + + ndata = curlx_calloc(1, (pa->capacity + growth) * sizeof(void *)); + if(!ndata) + return CURLE_OUT_OF_MEMORY; + if(pa->n) + memcpy(ndata, pa->data, pa->n * sizeof(void *)); + curlx_free(pa->data); + pa->data = ndata; + pa->capacity += growth; + } + DEBUGASSERT(pa->n < pa->capacity); + pa->data[pa->n++] = ptr; + return CURLE_OK; +} + +void *Curl_ptrarray_remove(struct ptrarray *pa, uint32_t idx) +{ + if(idx < pa->n) { + uint32_t rem = pa->n - idx - 1; + void *ptr = pa->data[idx]; + pa->data[idx] = NULL; + if(rem) + memmove(pa->data + idx, pa->data + idx + 1, rem * sizeof(void *)); + pa->n--; + if(!pa->n) + Curl_ptrarray_clear(pa); + return ptr; + } + return NULL; +} + +void *Curl_ptrarray_get(struct ptrarray *pa, uint32_t idx) +{ + return (idx < pa->n) ? pa->data[idx] : NULL; +} diff --git a/lib/ptrarray.h b/lib/ptrarray.h new file mode 100644 index 000000000000..5fcf810d8c86 --- /dev/null +++ b/lib/ptrarray.h @@ -0,0 +1,50 @@ +#ifndef HEADER_CURL_PTRARRAY_H +#define HEADER_CURL_PTRARRAY_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ +#include "curl_setup.h" + +/* An array of pointers with up to UINT32_MAX entries */ +struct ptrarray { + void **data; /* #capacity array of pointers */ + uint32_t n; + uint32_t capacity; +}; + +void Curl_ptrarray_init(struct ptrarray *pa); + +void Curl_ptrarray_clear(struct ptrarray *pa); + +uint32_t Curl_ptrarray_count(struct ptrarray *pa); + +CURLcode Curl_ptrarray_add(struct ptrarray *pa, void *ptr); + +void *Curl_ptrarray_remove(struct ptrarray *pa, uint32_t idx); + +void *Curl_ptrarray_get(struct ptrarray *pa, uint32_t idx); + +#define CURL_PTRARRAY_COUNT(a) ((a)->n) +#define CURL_PTRARRAY_GET(a, i) (((i) < ((a)->n)) ? (a)->data[(i)] : NULL) + +#endif /* HEADER_CURL_PTRARRAY_H */ diff --git a/lib/urldata.h b/lib/urldata.h index ab07696f4201..449a7bac334e 100644 --- a/lib/urldata.h +++ b/lib/urldata.h @@ -283,7 +283,6 @@ struct connectdata { * the connection is cleaned up (see Curl_hash_add2()).*/ struct Curl_hash meta_hash; - struct Curl_llist_node cshutdn_node; /* cshutdn list */ char *destination; /* hostname+port, used in conncache */ struct curltime created; /* creation time */ diff --git a/tests/data/test3211 b/tests/data/test3211 index 4e4271e455fd..48ec467611a6 100644 --- a/tests/data/test3211 +++ b/tests/data/test3211 @@ -6,6 +6,7 @@ unittest uint_bset u8_strset u32_ptrset +ptrarray @@ -15,7 +16,7 @@ u32_ptrset unittest -uint_bset, u8_strset and u32_ptrset unit tests +uint_bset, u8_strset, u32_ptrset and ptrarray unit tests diff --git a/tests/unit/unit3211.c b/tests/unit/unit3211.c index 0ec959ebc9e2..d10ad79401cc 100644 --- a/tests/unit/unit3211.c +++ b/tests/unit/unit3211.c @@ -26,6 +26,7 @@ #include "uint-bset.h" #include "u8_strset.h" #include "u32_ptrset.h" +#include "ptrarray.h" #include "curl_trc.h" static void t3211_check_bset(const char *name, uint32_t capacity, @@ -277,6 +278,45 @@ static void t3211_check_u32_ptrset(void) fail_unless(!Curl_u32_ptrset_count(&set), "ptrset cleared not empty"); } +static void t3211_check_ptrarray(void) +{ + struct ptrarray pa; + CURLcode result; + char d0 = '0', d1 = '1'; + uint32_t i; + + Curl_ptrarray_init(&pa); + fail_unless(!Curl_ptrarray_count(&pa), "ptrarray init not empty"); + + result = Curl_ptrarray_add(&pa, &d0); + fail_unless(!result, "ptrarray add0 failed"); + fail_unless(Curl_ptrarray_get(&pa, 0) == &d0, "ptrarray get0 failed"); + fail_unless(Curl_ptrarray_count(&pa) == 1, "ptrarray add0 count wrong"); + result = Curl_ptrarray_add(&pa, &d1); + fail_unless(!result, "ptrarray add1 failed"); + fail_unless(Curl_ptrarray_get(&pa, 1) == &d1, "ptrarray get1 failed"); + fail_unless(Curl_ptrarray_count(&pa) == 2, "ptrarray add1 count wrong"); + Curl_ptrarray_remove(&pa, 0); + fail_unless(Curl_ptrarray_count(&pa) == 1, "ptrarray rem count wrong"); + fail_unless(Curl_ptrarray_get(&pa, 1) == NULL, "ptrarray rem+get1 not NULL"); + fail_unless(Curl_ptrarray_get(&pa, 0) == &d1, "ptrarray rem+get0 failed"); + Curl_ptrarray_remove(&pa, 0); + fail_unless(Curl_ptrarray_count(&pa) == 0, "ptrarray rem2 count wrong"); + + for(i = 0; i < 300; ++i) { + result = Curl_ptrarray_add(&pa, (i & 0x1) ? &d1 : &d0); + fail_unless(!result, "ptrarray loop add failed"); + } + fail_unless(Curl_ptrarray_count(&pa) == 300, "ptrarray loop count wrong"); + for(i = 0; i < 300; ++i) { + fail_unless(Curl_ptrarray_get(&pa, i) == ((i & 0x1) ? &d1 : &d0), + "ptrarray loop get failed"); + } + + Curl_ptrarray_clear(&pa); + fail_unless(!Curl_ptrarray_count(&pa), "ptrarray clear not empty"); +} + static CURLcode test_unit3211(const char *arg) { UNITTEST_BEGIN_SIMPLE @@ -302,6 +342,7 @@ static CURLcode test_unit3211(const char *arg) t3211_check_u8_strset(); t3211_check_u32_ptrset(); + t3211_check_ptrarray(); UNITTEST_END_SIMPLE } From aeb3e9f011f44295e8c6138980ef9490b112acd3 Mon Sep 17 00:00:00 2001 From: Stefan Eissing Date: Tue, 6 Oct 2026 12:21:41 +0200 Subject: [PATCH 406/481] curl_multi_perform: doc update - fix typo - reference CURLMOPT_NOTIFYFUNCTION use Closes #23283 --- docs/libcurl/curl_multi_perform.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/docs/libcurl/curl_multi_perform.md b/docs/libcurl/curl_multi_perform.md index e0335d81a868..64ec10c0f4cd 100644 --- a/docs/libcurl/curl_multi_perform.md +++ b/docs/libcurl/curl_multi_perform.md @@ -5,6 +5,7 @@ Title: curl_multi_perform Section: 3 Source: libcurl See-also: + - CURLMOPT_NOTIFYFUNCTION (3) - curl_multi_add_handle (3) - curl_multi_cleanup (3) - curl_multi_fdset (3) @@ -53,8 +54,12 @@ added handle fails quickly, it may never be counted as a running_handle. You could use curl_multi_info_read(3) to track actual status of the added handles in that case. +CURLMOPT_NOTIFYFUNCTION(3) is an alternative to tracking *running_handles* +where you register a callback to be notified when transfers are done and +curl_multi_info_read(3) should be called. + When *running_handles* is set to zero (0) on the return of this function, -there is no longer any transfers in progress. +there are no longer any transfers in progress. When this function returns error, the state of all transfers are uncertain and they cannot be continued. curl_multi_perform(3) should not be called From 0673aa236af3e89a5ae27bc114c0b724f7cc3a93 Mon Sep 17 00:00:00 2001 From: Daniel Stenberg Date: Mon, 5 Oct 2026 16:58:17 +0200 Subject: [PATCH 407/481] url: check syntax before applying default-protocol Otherwise a non-conformant URL could be turned into a valid one by prepending the scheme, causing surprises. Verified by test 4039 and 4040 Reported-by: Samuel Henrique Closes #23273 --- docs/libcurl/opts/CURLOPT_DEFAULT_PROTOCOL.md | 6 +- lib/url.c | 101 ++++++++++++------ tests/data/Makefile.am | 2 + tests/data/test4039 | 27 +++++ tests/data/test4040 | 27 +++++ 5 files changed, 129 insertions(+), 34 deletions(-) create mode 100644 tests/data/test4039 create mode 100644 tests/data/test4040 diff --git a/docs/libcurl/opts/CURLOPT_DEFAULT_PROTOCOL.md b/docs/libcurl/opts/CURLOPT_DEFAULT_PROTOCOL.md index 92930c07e47e..d5e8c75d852e 100644 --- a/docs/libcurl/opts/CURLOPT_DEFAULT_PROTOCOL.md +++ b/docs/libcurl/opts/CURLOPT_DEFAULT_PROTOCOL.md @@ -29,8 +29,8 @@ CURLcode curl_easy_setopt(CURL *handle, CURLOPT_DEFAULT_PROTOCOL, # DESCRIPTION -This option tells libcurl to use *protocol* if the URL is missing a scheme -name. +This option tells libcurl to use *protocol* if the URL set with CURLOPT_URL(3) +misses a scheme name. Use one of these protocol (scheme) names: @@ -55,6 +55,8 @@ option. Using this option multiple times makes the last set string override the previous ones. Set it to NULL to disable its use again. +This option does *not* adjust the scheme for URLs set with CURLOPT_CURLU(3). + # DEFAULT NULL (make a guess based on the host) diff --git a/lib/url.c b/lib/url.c index 3a9464cfe926..a474c76538e7 100644 --- a/lib/url.c +++ b/lib/url.c @@ -2061,7 +2061,7 @@ static CURLcode url_set_data_origin_and_creds(struct Curl_easy *data) { CURLcode result = CURLE_OK; CURLU *uh; - CURLUcode uc; + CURLUcode uc = CURLUE_OK; bool use_set_uh = (data->set.uh && !data->state.this_is_a_follow); uint16_t port_override = data->state.allow_port ? data->set.use_port : 0; uint32_t scope_id = 0; @@ -2086,41 +2086,78 @@ static CURLcode url_set_data_origin_and_creds(struct Curl_easy *data) goto out; } - /* Calculate the *real* URL this transfer uses, applying defaults - * where information is missing. */ - if(CURL_EASY_STR(data, STRING_DEFAULT_PROTOCOL) && - !Curl_is_absolute_url(Curl_bufref_ptr(&data->state.url), NULL, 0, TRUE)) { - char *url = curl_maprintf("%s://%s", - CURL_EASY_STR(data, STRING_DEFAULT_PROTOCOL), - Curl_bufref_ptr(&data->state.url)); - if(!url) { - result = CURLE_OUT_OF_MEMORY; - goto out; - } - Curl_bufref_set(&data->state.url, url, 0, curl_free); - } - if(!use_set_uh) { - char *newurl; - uc = curl_url_set(uh, CURLUPART_URL, Curl_bufref_ptr(&data->state.url), - (unsigned int)(CURLU_GUESS_SCHEME | - CURLU_NON_SUPPORT_SCHEME | - (data->set.disallow_username_in_url ? - CURLU_DISALLOW_USER : 0) | - (data->set.path_as_is ? CURLU_PATH_AS_IS : 0))); - if(uc) { - failf(data, "URL rejected: %s", curl_url_strerror(uc)); - result = Curl_uc_to_curlcode(uc); - goto out; + bool prepend_sheme = FALSE; + /* special-case when 'file' is default as a normal path is not a valid URL + without the 'file' scheme present */ + if(curl_strequal("file", CURL_EASY_STR(data, STRING_DEFAULT_PROTOCOL)) && + !Curl_is_absolute_url(Curl_bufref_ptr(&data->state.url), NULL, 0, TRUE)) + prepend_sheme = TRUE; + else { + uc = curl_url_set(uh, CURLUPART_URL, Curl_bufref_ptr(&data->state.url), + (unsigned int) + CURLU_GUESS_SCHEME | + CURLU_NON_SUPPORT_SCHEME | + (data->set.disallow_username_in_url ? + CURLU_DISALLOW_USER : 0) | + (data->set.path_as_is ? CURLU_PATH_AS_IS : 0)); + if(uc) { + failf(data, "URL rejected: %s", curl_url_strerror(uc)); + result = Curl_uc_to_curlcode(uc); + goto out; + } + + if(CURL_EASY_STR(data, STRING_DEFAULT_PROTOCOL)) { + /* check if we there was a scheme provided */ + char *scheme; + uc = curl_url_get(uh, CURLUPART_SCHEME, &scheme, + CURLU_NO_GUESS_SCHEME); + if(uc == CURLUE_NO_SCHEME) + prepend_sheme = TRUE; + curl_free(scheme); + if(uc && uc != CURLUE_NO_SCHEME) { + result = Curl_uc_to_curlcode(uc); + goto out; + } + } } - /* after it was parsed, get the generated normalized version */ - uc = curl_url_get(uh, CURLUPART_URL, &newurl, CURLU_GET_EMPTY); - if(uc) { - result = Curl_uc_to_curlcode(uc); - goto out; + if(prepend_sheme) { + char *url = curl_maprintf("%s://%s", + CURL_EASY_STR(data, STRING_DEFAULT_PROTOCOL), + Curl_bufref_ptr(&data->state.url)); + if(!url) { + result = CURLE_OUT_OF_MEMORY; + goto out; + } + uc = curl_url_set(uh, CURLUPART_URL, url, + (unsigned int) + CURLU_GUESS_SCHEME | + CURLU_NON_SUPPORT_SCHEME | + (data->set.disallow_username_in_url ? + CURLU_DISALLOW_USER : 0) | + (data->set.path_as_is ? CURLU_PATH_AS_IS : 0)); + curl_free(url); + if(uc) { + failf(data, "URL rejected: %s", curl_url_strerror(uc)); + result = Curl_uc_to_curlcode(uc); + goto out; + } + } + { + char *newurl; + /* after it was parsed, and possibly given a new scheme, get the + normalized version */ + uc = curl_url_get(uh, CURLUPART_URL, &newurl, CURLU_GET_EMPTY); + if(uc) { + result = Curl_uc_to_curlcode(uc); + goto out; + } + if(prepend_sheme) + infof(data, "Applied default protocol '%s'", + CURL_EASY_STR(data, STRING_DEFAULT_PROTOCOL)); + Curl_bufref_set(&data->state.url, newurl, 0, curl_free); } - Curl_bufref_set(&data->state.url, newurl, 0, curl_free); } #ifdef USE_IPV6 diff --git a/tests/data/Makefile.am b/tests/data/Makefile.am index c5469f6d96bb..388de64dcc6f 100644 --- a/tests/data/Makefile.am +++ b/tests/data/Makefile.am @@ -2136,6 +2136,8 @@ TESTCASES = \ test3596 \ test4000 \ test4001 \ + test4039 \ + test4040 \ test4644 \ test4656 \ test4657 \ diff --git a/tests/data/test4039 b/tests/data/test4039 new file mode 100644 index 000000000000..9a30dd6a33a7 --- /dev/null +++ b/tests/data/test4039 @@ -0,0 +1,27 @@ + + + + +--proto-default + + + + + +http + + +--proto-default http with an invalid URL + + +/example.com/ --proto-default http + + + +# Verify data after the test has been "shot" + + +3 + + + diff --git a/tests/data/test4040 b/tests/data/test4040 new file mode 100644 index 000000000000..ae39d378e1a8 --- /dev/null +++ b/tests/data/test4040 @@ -0,0 +1,27 @@ + + + + +--proto-default + + + + + +--proto-default file and invalid URL + + +file + + +--proto-default file remote.example/etc/passwd + + + +# Verify data after the test has been "shot" + + +3 + + + From 65685bf38d9e67f412bc4825837d846c43ec56f5 Mon Sep 17 00:00:00 2001 From: Viktor Szakats Date: Tue, 6 Oct 2026 15:23:30 +0200 Subject: [PATCH 408/481] tool_util: add Unicode support to `tool_execpath()` To fix opening `.curlrc`/`_curlrc` next to the curl tool, and to make the CA safe search built-time option find `curl-ca-bundle.crt` as expected when the curl tool resides on a non-ASCII path when using a Unicode-enabled build. The CA safe search build-time option is disabled by default. The issue has been discovered while working on enabling it by default. Follow-up to d40f4e15e73f906cd06919da84e535f52637c1a1 Follow-up to 78882e46429e05c2a258a5440f06644f588cc224 Cherry-picked from #23277 Closes #23287 --- src/tool_util.c | 49 ++++++++++++++++++++++++++++++++++--------------- 1 file changed, 34 insertions(+), 15 deletions(-) diff --git a/src/tool_util.c b/src/tool_util.c index 5a8a3bc4dbd1..25aa636175b3 100644 --- a/src/tool_util.c +++ b/src/tool_util.c @@ -111,26 +111,45 @@ int toolx_ftruncate_djgpp(int fd, curl_off_t where) FILE *tool_execpath(const char *filename, char **pathp) { static char filebuffer[512]; +#ifdef UNICODE + TCHAR filebuffer_t[sizeof(filebuffer)]; +#else + TCHAR *filebuffer_t = filebuffer; +#endif unsigned long len; /* Get the filename of our executable. GetModuleFileName is already declared - * via inclusions done in setup header file. We assume that we are using - * the ASCII version here. - */ - len = GetModuleFileNameA(0, filebuffer, sizeof(filebuffer)); + via inclusions done in setup header file. */ + len = GetModuleFileName(0, filebuffer_t, sizeof(filebuffer)); if(len > 0 && len < sizeof(filebuffer)) { - /* We got a valid filename - get the directory part */ - char *lastdirchar = strrchr(filebuffer, DIR_CHAR[0]); - if(lastdirchar) { - size_t remaining; - *lastdirchar = 0; - /* If we have enough space, build the RC filename */ - remaining = sizeof(filebuffer) - strlen(filebuffer); - if(strlen(filename) < remaining - 1) { - curl_msnprintf(lastdirchar, remaining, "%s%s", DIR_CHAR, filename); - *pathp = filebuffer; - return curlx_fopen(filebuffer, FOPEN_READTEXT); +#ifdef UNICODE + char *fn = curlx_convert_tchar_to_UTF8(filebuffer_t); + if(fn) { + size_t fn_len = strlen(fn); + if(fn_len >= sizeof(filebuffer)) { + curlx_free(fn); + return NULL; } + curlx_strcopy(filebuffer, sizeof(filebuffer), fn, fn_len); + curlx_free(fn); +#endif + { + /* We got a valid filename - get the directory part */ + char *lastdirchar = strrchr(filebuffer, DIR_CHAR[0]); + if(lastdirchar) { + size_t remaining; + *lastdirchar = 0; + /* If we have enough space, build the RC filename */ + remaining = sizeof(filebuffer) - strlen(filebuffer); + if(strlen(filename) < remaining - 1) { + curl_msnprintf(lastdirchar, remaining, "%s%s", DIR_CHAR, filename); + *pathp = filebuffer; + return curlx_fopen(filebuffer, FOPEN_READTEXT); + } + } + } +#ifdef UNICODE } +#endif } return NULL; From 63f67c8ab3de3a71f47f983d77fab3edec03adc3 Mon Sep 17 00:00:00 2001 From: Viktor Szakats Date: Sun, 1 Feb 2026 02:41:43 +0100 Subject: [PATCH 409/481] tool: add Unicode and long-filename support to `--create-dir` (Windows) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Example: ``` > curl --create-dirs --etag-save aßcő/very-long-....-dir/etags.txt https://curl.se/ ``` Ref: https://learn.microsoft.com/cpp/c-runtime-library/reference/mkdir-wmkdir Closes #23289 --- lib/curlx/win32-fopen.c | 32 ++++++++++++++++++++++++++++++++ lib/curlx/win32-fopen.h | 7 +++++++ src/tool_dirhie.c | 13 +++---------- 3 files changed, 42 insertions(+), 10 deletions(-) diff --git a/lib/curlx/win32-fopen.c b/lib/curlx/win32-fopen.c index 24738e9a9351..9c683fc64888 100644 --- a/lib/curlx/win32-fopen.c +++ b/lib/curlx/win32-fopen.c @@ -40,6 +40,7 @@ int curlx_fseek(void *stream, curl_off_t offset, int whence) #ifdef _WIN32 +#include /* for _mkdir(), _wmkdir() */ #include /* for _SH_DENYNO */ #include "curlx/multibyte.h" @@ -467,6 +468,37 @@ int curlx_win32_stat(const char *path, curlx_struct_stat *buffer) return res; } +int curlx_win32_mkdir(const char *path) +{ + int res = -1; + TCHAR *fixed = NULL; + const TCHAR *target = NULL; + +#ifdef _UNICODE + wchar_t *path_w = curlx_convert_UTF8_to_wchar(path); + if(path_w) { + if(fix_excessive_path(path_w, &fixed)) + target = fixed; + else + target = path_w; + res = _wmkdir(target); + curlx_free(path_w); + } + else + /* !checksrc! disable ERRNOVAR 1 */ + errno = EINVAL; +#else + if(fix_excessive_path(path, &fixed)) + target = fixed; + else + target = path; + res = _mkdir(target); +#endif + + CURLX_FREE(fixed); + return res; +} + #if !defined(CURL_DISABLE_HTTP) || !defined(CURL_DISABLE_COOKIES) || \ !defined(CURL_DISABLE_ALTSVC) /* rename() on Windows does not overwrite, so we cannot use it here. diff --git a/lib/curlx/win32-fopen.h b/lib/curlx/win32-fopen.h index 469d3c881303..1548f7a18675 100644 --- a/lib/curlx/win32-fopen.h +++ b/lib/curlx/win32-fopen.h @@ -53,6 +53,7 @@ FILE *curlx_win32_freopen(const char *filename, const char *mode, FILE *fp); int curlx_win32_stat(const char *path, curlx_struct_stat *buffer); int curlx_win32_open(const char *filename, int oflag, ...); int curlx_win32_rename(const char *oldpath, const char *newpath); +int curlx_win32_mkdir(const char *path); #define CURLX_FOPEN_LOW curlx_win32_fopen #define CURLX_FREOPEN_LOW curlx_win32_freopen #define CURLX_FDOPEN_LOW _fdopen @@ -60,6 +61,7 @@ int curlx_win32_rename(const char *oldpath, const char *newpath); #define curlx_open curlx_win32_open #define curlx_close _close #define curlx_rename curlx_win32_rename +#define curlx_mkdir(x, y) curlx_win32_mkdir(x) #else #define curlx_fstat fstat #define curlx_struct_stat struct stat @@ -70,6 +72,11 @@ int curlx_win32_rename(const char *oldpath, const char *newpath); #define curlx_open open #define curlx_close close #define curlx_rename rename +#if defined(MSDOS) && !defined(__DJGPP__) +#define curlx_mkdir(x, y) mkdir(x) +#else +#define curlx_mkdir mkdir +#endif #endif #ifdef CURL_MEMDEBUG diff --git a/src/tool_dirhie.c b/src/tool_dirhie.c index 6e0a0e5e3763..d4b8947f99be 100644 --- a/src/tool_dirhie.c +++ b/src/tool_dirhie.c @@ -27,17 +27,8 @@ #include "tool_msgs.h" #ifdef UNITTESTS -# define toolx_mkdir(x, y) create_dir_hierarchy_trace_mkdir(x) -#elif defined(_WIN32) -# include -# define toolx_mkdir(x, y) _mkdir(x) -#elif defined(MSDOS) && !defined(__DJGPP__) -# define toolx_mkdir(x, y) mkdir(x) -#else -# define toolx_mkdir mkdir -#endif +#define toolx_mkdir(x, y) create_dir_hierarchy_trace_mkdir(x) -#ifdef UNITTESTS static struct dynbuf mkdir_results; UNITTEST struct dynbuf *create_dir_hierarchy_trace_dynres(void) @@ -51,6 +42,8 @@ static int create_dir_hierarchy_trace_mkdir(const char *dir) curlx_dyn_add(&mkdir_results, dir) || curlx_dyn_add(&mkdir_results, "|") ? -1 : 0; } +#else +#define toolx_mkdir curlx_mkdir #endif static void show_dir_errno(const char *name) From 21b9f031580a323c04f3bff4b631f1a69d256e2b Mon Sep 17 00:00:00 2001 From: Viktor Szakats Date: Thu, 1 Oct 2026 09:01:00 +0200 Subject: [PATCH 410/481] GHA/linux: run `randcurl` step in the background To make the `address-sanitizer` and `memory-sanitizer` jobs finish earlier. Not every run seems to be benefitting, but in some runs it save about 1 minute per job. Closes #23204 --- .github/workflows/linux.yml | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 776d0661f8be..55145e2f1b60 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -889,6 +889,14 @@ jobs: make -C bld V=1 install fi + - name: 'randcurl' + if: ${{ contains(matrix.build.install_steps, 'randcurl') }} + background: true + run: | + mkdir run + cd run + ../.github/scripts/randcurl.pl 60 ../bld/src/curl + - name: 'build tests' if: ${{ !contains(matrix.build.install_steps, 'skipall') }} run: | @@ -955,13 +963,6 @@ jobs: make -C bld V=1 pytest-ci fi - - name: 'randcurl' - if: ${{ contains(matrix.build.install_steps, 'randcurl') }} - run: | - mkdir run - cd run - ../.github/scripts/randcurl.pl 60 ../bld/src/curl - - name: 'build examples' if: ${{ !contains(matrix.build.install_packages, 'valgrind') && !contains(matrix.build.name, '!examples') }} run: | From e90bb2ff182fe4cc098abf546a5eb84056eb0e5b Mon Sep 17 00:00:00 2001 From: Dan Fandrich Date: Tue, 6 Oct 2026 09:29:43 -0700 Subject: [PATCH 411/481] tests: clean up Python test code - stop shadowing Python built-in names for variables - remove redundant len() calls - indent consistently - alphabetize indent order - use one line instead of four in conditional assigments - remove a redundant exception - remove redundant parenthesis Closes #23290 --- tests/http/scorecard.py | 26 +++++++++++++------------- tests/http/test_02_download.py | 25 ++++++++----------------- tests/http/test_06_eyeballs.py | 2 +- tests/http/test_11_unix.py | 2 +- tests/http/test_17_ssl_use.py | 4 ++-- tests/http/test_20_websockets.py | 11 +++++------ tests/http/test_31_vsftpds.py | 2 +- tests/http/test_32_ftps_vsftpd.py | 2 +- tests/http/testenv/__init__.py | 2 +- tests/http/testenv/certs.py | 2 +- tests/http/testenv/curl.py | 6 +++--- tests/http/testenv/h2o.py | 4 ++-- tests/negtelnetserver.py | 6 +++--- 13 files changed, 42 insertions(+), 52 deletions(-) diff --git a/tests/http/scorecard.py b/tests/http/scorecard.py index 55497cb90f24..dc1f1c3a6cab 100755 --- a/tests/http/scorecard.py +++ b/tests/http/scorecard.py @@ -106,40 +106,40 @@ def fmt_reqs(cls, val): @classmethod def mk_mbs_cell(cls, samples, profiles, errors): - val = mean(samples) if len(samples) else -1 + val = mean(samples) if samples else -1 cell = { 'val': val, 'sval': Card.fmt_mbs(val) if val >= 0 else '--', } - if len(profiles): + if profiles: cell['stats'] = RunProfile.AverageStats(profiles) - if len(errors): + if errors: cell['errors'] = errors return cell @classmethod def mk_speed_cell(cls, samples, profiles, errors, limit): - val = mean(samples) if len(samples) else -1 + val = mean(samples) if samples else -1 cell = { 'val': val, 'sval': Card.fmt_speed_result(val, limit) if val >= 0 else '--', } - if len(profiles): + if profiles: cell['stats'] = RunProfile.AverageStats(profiles) - if len(errors): + if errors: cell['errors'] = errors return cell @classmethod def mk_reqs_cell(cls, samples, profiles, errors): - val = mean(samples) if len(samples) else -1 + val = mean(samples) if samples else -1 cell = { 'val': val, 'sval': Card.fmt_reqs(val) if val >= 0 else '--', } - if len(profiles): + if profiles: cell['stats'] = RunProfile.AverageStats(profiles) - if len(errors): + if errors: cell['errors'] = errors return cell @@ -224,7 +224,7 @@ def print_score_table(cls, score): if 'errors' in cell: errors.extend(cell['errors']) print() - if len(errors): + if errors: print(f'Errors: {errors}') @@ -314,9 +314,9 @@ def handshakes(self) -> Dict[str, Any]: else: errors.append(f'exit={r.exit_code}') props[authority][f'{ipv}-connect'] = mean(c_samples) \ - if len(c_samples) else -1 + if c_samples else -1 props[authority][f'{ipv}-handshake'] = mean(hs_samples) \ - if len(hs_samples) else -1 + if hs_samples else -1 props[authority][f'{ipv}-errors'] = errors self.info('ok.\n') return props @@ -652,7 +652,7 @@ def requests(self, count: int, meta: Dict[str, Any]) -> Dict[str, Any]: 'sval': Card.fmt_size(fsize) }, { 'val': count, - 'sval': f'{count}', + 'sval': f'{count}', } ] self.info('requests, max parallel...') diff --git a/tests/http/test_02_download.py b/tests/http/test_02_download.py index f4e896b9ccba..106094fd46c8 100644 --- a/tests/http/test_02_download.py +++ b/tests/http/test_02_download.py @@ -318,10 +318,7 @@ def test_02_23a_lib_abort_paused(self, env: Env, httpd, nghttpx, proto): pytest.skip("fails in CI, but works locally for unknown reasons") count = 10 max_parallel = 5 - if proto in Env.http_mplx_protos(): - pause_offset = 64 * 1024 - else: - pause_offset = 12 * 1024 + pause_offset = 1024 * (64 if proto in Env.http_mplx_protos() else 12) docname = 'data-1m' url = f'https://localhost:{env.https_port}/{docname}' client = LocalClient(name='cli_hx_download', env=env) @@ -344,10 +341,7 @@ def test_02_23b_lib_abort_offset(self, env: Env, httpd, nghttpx, proto): pytest.skip("fails in CI, but works locally for unknown reasons") count = 10 max_parallel = 5 - if proto in Env.http_mplx_protos(): - abort_offset = 64 * 1024 - else: - abort_offset = 12 * 1024 + abort_offset = 1024 * (64 if proto in Env.http_mplx_protos() else 12) docname = 'data-1m' url = f'https://localhost:{env.https_port}/{docname}' client = LocalClient(name='cli_hx_download', env=env) @@ -370,10 +364,7 @@ def test_02_23c_lib_fail_offset(self, env: Env, httpd, nghttpx, proto): pytest.skip("fails in CI, but works locally for unknown reasons") count = 10 max_parallel = 5 - if proto in Env.http_mplx_protos(): - fail_offset = 64 * 1024 - else: - fail_offset = 12 * 1024 + fail_offset = 1024 * (64 if proto in Env.http_mplx_protos() else 12) docname = 'data-1m' url = f'https://localhost:{env.https_port}/{docname}' client = LocalClient(name='cli_hx_download', env=env) @@ -732,11 +723,11 @@ def test_02_37_pending_timeout(self, env: Env, httpd): '/curltest/tweak/?&delay=3s' url2 = f'https://{env.authority_for(env.domain1, proto)}/data.json' r = curl.http_download(urls=[url1, url2], alpn_proto=proto, extra_args=[ - '--http1.1', '--parallel' - ], url_options={ - url1: ['--max-time', '10'], - url2: ['--max-time', '1'] - }) + '--http1.1', '--parallel'], + url_options={ + url1: ['--max-time', '10'], + url2: ['--max-time', '1'] + }) r.check_exit_code(28) xfers = {stat['xfer_id']: stat for stat in r.stats} assert xfers[0]['http_code'] == 200, f'{r.stats[0]}' # succeeded diff --git a/tests/http/test_06_eyeballs.py b/tests/http/test_06_eyeballs.py index b72a89d75e4c..afead121139c 100644 --- a/tests/http/test_06_eyeballs.py +++ b/tests/http/test_06_eyeballs.py @@ -133,7 +133,7 @@ def test_06_13_timers(self, env: Env): # timeouts being set here failed_attempts = [line for line in r.trace_lines if re.match(r'.*checked connect attempts: 0 ongoing', line)] - if len(failed_attempts): + if failed_attempts: # github CI fails right away with "Network is unreachable", slackers... assert len(failed_attempts) == 3, f'found: {"".join(failed_attempts)}\n{r.dump_logs()}' else: diff --git a/tests/http/test_11_unix.py b/tests/http/test_11_unix.py index 436da8617c70..8b6d7877bc15 100644 --- a/tests/http/test_11_unix.py +++ b/tests/http/test_11_unix.py @@ -80,7 +80,7 @@ def _process(self): finally: c.close() - except (ConnectionAbortedError, OSError): + except OSError: self._done = True diff --git a/tests/http/test_17_ssl_use.py b/tests/http/test_17_ssl_use.py index 82310d8f13a2..85f7909a8af6 100644 --- a/tests/http/test_17_ssl_use.py +++ b/tests/http/test_17_ssl_use.py @@ -232,8 +232,8 @@ def gen_test_17_07_list(): 'TLSv1.2': 'TLSv1.2'}.items(): for [cid13, ciphers13, succeed13] in tls13_tests: for [cid12, ciphers12, succeed12] in tls12_tests: - id = f'{tls_id}-{cid13}-{cid12}' - ret.append(pytest.param(tls_proto, ciphers13, ciphers12, succeed13, succeed12, id=id)) + testid = f'{tls_id}-{cid13}-{cid12}' + ret.append(pytest.param(tls_proto, ciphers13, ciphers12, succeed13, succeed12, id=testid)) return ret @pytest.mark.parametrize( diff --git a/tests/http/test_20_websockets.py b/tests/http/test_20_websockets.py index 030d1015af35..9ea9edc5d56a 100644 --- a/tests/http/test_20_websockets.py +++ b/tests/http/test_20_websockets.py @@ -275,12 +275,11 @@ def srv(): hashlib.sha1(k + b"258EAFA5-E914-47DA-95CA-C5AB0DC85B11").digest() ).decode() c.sendall( - ( - "HTTP/1.1 101 Switching Protocols\r\n" - "Upgrade: websocket\r\n" - "Connection: Upgrade\r\n" - f"Sec-WebSocket-Accept: {a}\r\n\r\n" - ).encode() + "HTTP/1.1 101 Switching Protocols\r\n" + "Upgrade: websocket\r\n" + "Connection: Upgrade\r\n" + f"Sec-WebSocket-Accept: {a}\r\n\r\n" + .encode() ) f = b"\x89\x00" * 65536 # PING frames, many diff --git a/tests/http/test_31_vsftpds.py b/tests/http/test_31_vsftpds.py index 113e164e1938..c3032e4924c3 100644 --- a/tests/http/test_31_vsftpds.py +++ b/tests/http/test_31_vsftpds.py @@ -265,7 +265,7 @@ def test_31_10_upload_stdin(self, env: Env, vsftpds: VsFTPD, indata): assert os.path.exists(dstfile) with open(dstfile) as fd: destdata = fd.readlines() - expdata = [indata] if len(indata) else [] + expdata = [indata] if indata else [] assert expdata == destdata, f'expected: {expdata}, got: {destdata}' def test_31_11_download_non_existing(self, env: Env, vsftpds: VsFTPD): diff --git a/tests/http/test_32_ftps_vsftpd.py b/tests/http/test_32_ftps_vsftpd.py index 1328a0b34b1b..9301b5265ade 100644 --- a/tests/http/test_32_ftps_vsftpd.py +++ b/tests/http/test_32_ftps_vsftpd.py @@ -275,7 +275,7 @@ def test_32_10_upload_stdin(self, env: Env, vsftpds: VsFTPD, indata): assert os.path.exists(dstfile) with open(dstfile) as fd: destdata = fd.readlines() - expdata = [indata] if len(indata) else [] + expdata = [indata] if indata else [] assert expdata == destdata, f'expected: {expdata}, got: {destdata}' def test_32_11_download_non_existing(self, env: Env, vsftpds: VsFTPD): diff --git a/tests/http/testenv/__init__.py b/tests/http/testenv/__init__.py index 834230a853bd..ae83d681abad 100644 --- a/tests/http/testenv/__init__.py +++ b/tests/http/testenv/__init__.py @@ -36,7 +36,7 @@ from .dante import Dante from .dnsd import Dnsd from .env import Env -from .h2o import H2oServer, H2oProxy +from .h2o import H2oProxy, H2oServer from .httpd import Httpd from .nghttpx import Nghttpx, NghttpxFwd, NghttpxQuic from .sshd import Sshd diff --git a/tests/http/testenv/certs.py b/tests/http/testenv/certs.py index 84fb4065d64f..3cf057b499fc 100644 --- a/tests/http/testenv/certs.py +++ b/tests/http/testenv/certs.py @@ -213,7 +213,7 @@ def hashdir(self) -> Optional[str]: def get_first(self, name) -> Optional['Credentials']: creds = self._store.get_credentials_for_name(name) if self._store else [] - return creds[0] if len(creds) else None + return creds[0] if creds else None def get_credentials_for_name(self, name) -> List['Credentials']: return self._store.get_credentials_for_name(name) if self._store else [] diff --git a/tests/http/testenv/curl.py b/tests/http/testenv/curl.py index 34b28b9533ef..9b7f7822d5b7 100644 --- a/tests/http/testenv/curl.py +++ b/tests/http/testenv/curl.py @@ -55,9 +55,9 @@ def AverageStats(cls, profiles: List['RunProfile']): stats = [p.stats for p in profiles] for key in cls.STAT_KEYS: vals = [s[key] for s in stats] - avg[key] = mean(vals) if len(vals) else 0.0 + avg[key] = mean(vals) if vals else 0.0 vals = [s['rss-max'] for s in [p.stats for p in profiles]] - avg['rss-max'] = mean(vals) if len(vals) else 0 + avg['rss-max'] = mean(vals) if vals else 0 return avg def __init__(self, pid: int, started_at: datetime, run_dir): @@ -380,7 +380,7 @@ def stats(self) -> List: @property def total_connects(self) -> Optional[int]: - if len(self.stats): + if self.stats: n = 0 for stat in self.stats: n += stat['num_connects'] diff --git a/tests/http/testenv/h2o.py b/tests/http/testenv/h2o.py index 8b77a9ae2354..377e84914dd4 100644 --- a/tests/http/testenv/h2o.py +++ b/tests/http/testenv/h2o.py @@ -86,8 +86,8 @@ def dump_logs(self): "-------------------------------------------"]) self._dump_file(self._stderr, lines) lines.extend(["", - f"errorlog of {self._name}", - "-------------------------------------------"]) + f"errorlog of {self._name}", + "-------------------------------------------"]) self._dump_file(self._error_log, lines) lines.append("") return lines diff --git a/tests/negtelnetserver.py b/tests/negtelnetserver.py index 44ae2eeb75c8..4d7b49604aba 100755 --- a/tests/negtelnetserver.py +++ b/tests/negtelnetserver.py @@ -117,11 +117,11 @@ def __init__(self, tcp): self.tcp = tcp self.state = self.NO_NEG - def recv(self, bytes): + def recv(self, nbytes): """ Read bytes from TCP, handling negotiation sequences. - :param bytes: Number of bytes to read + :param nbytes: Number of bytes to read :return: a buffer of bytes """ buffer = bytearray() @@ -130,7 +130,7 @@ def recv(self, bytes): # Keep looping while we can, and until we have something to give back # to the caller. while len(buffer) == 0: - data = self.tcp.recv(bytes) + data = self.tcp.recv(nbytes) if not data: # TCP failed to give us any data. Break out. break From 50d1aa45a9103734821182e44379f751df402e79 Mon Sep 17 00:00:00 2001 From: Daniel Stenberg Date: Wed, 7 Oct 2026 07:45:22 +0200 Subject: [PATCH 412/481] RELEASE-NOTES: synced --- RELEASE-NOTES | 53 +++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 43 insertions(+), 10 deletions(-) diff --git a/RELEASE-NOTES b/RELEASE-NOTES index a127afd684ff..7d4a7b3cdd8d 100644 --- a/RELEASE-NOTES +++ b/RELEASE-NOTES @@ -5,7 +5,7 @@ curl and libcurl 8.23.0 curl_easy_setopt() options: 312 Public functions in libcurl: 100 Authors: 1538 - Contributors: 3831 + Contributors: 3835 This release includes the following changes: @@ -41,15 +41,19 @@ This release includes the following bugfixes: o conncache: never discard a connection still in use [208] o conncache: remove bundle dest [50] o connectdata: use less curltime [34] + o cookie: accept and ignore values for 'secure' and 'httponly' [187] o cookie: accept slightly longer lines in cookie jars [105] + o cookie: cookies for a PSL domain from jar just limits tailmatch [176] o cookie: ignore individual cookie errors when loading [6] o cpool: do not use admin handle for conn lookup [299] o cshutdown: make it smaller [60] + o cshutdown: use ptrarray instead of llist [156] o curl-openssl.m4: sync LibreSSL detection method with rest of forks [218] o curl_addrinfo: drop redundant parentheses [41] o curl_easy_send/recv: check pointer arg [175] o curl_fopen: restore the uid and gid checks [70] o curl_formadd.md: mention the strlen() for names as well [29] + o curl_multi_perform: doc update [148] o curl_setup.h: drop compatibility raw C flag `ENABLE_IPV6` [260] o CURLINFO_EFFECTIVE_URL.md: mention a short lifetime [116] o CURLOPT*ISSUERCERT*: not verified cryptographically [28] @@ -62,6 +66,7 @@ This release includes the following bugfixes: o cw-pause: fix O(N^2) list traversal in cw_pause_flush [137] o DEPENDENCIES.md: fix release date for valgrind 3.0.0 to 2005-08-03 o DEPRECATE.md: reflect SMB removal [252] + o DEPRECATE: Common Name support in TLS certificates for OpenSSL [172] o digest: check peer equality for staleness [24] o digest: fix maximum length quoted value parsing [22] o digest: parse without storing 'value' in local buffer [20] @@ -70,7 +75,9 @@ This release includes the following bugfixes: o dnscache: check hostname on lookup [209] o docs: add the missing .html to the url-encode book link [3] o docs: document and align the blocking situations [163] + o docs: SSL session import/file updates [199] o doh: reject undersized HTTPS RDATA [11] + o dynhds: check size overflows [198] o dynhds: grow header array geometrically [73] o easy: duplicating a zero byte memory area needs no malloc [91] o easy: make ca_cache_timeout standalone [51] @@ -153,6 +160,7 @@ This release includes the following bugfixes: o netrc: empty quoted values now produce a valid empty string [62] o netrc: survive comment-only .netrc file [93] o ngtcp2: shrink max sendbuf [13] + o ngtcp2: use stream in userdata [185] o openldap: hand it a dup'd socket, not curl's own o openssl/gtls: reject CRLfile with native CA store, matching rustls [27] o openssl: fix OpenSSL v4.1 no-deprecated build [190] @@ -171,7 +179,9 @@ This release includes the following bugfixes: o perf-tests: output two-digital decimals always [96] o pingpong: add a max loop counter for Curl_pp_readresp [113] o pytest: check server status without ssl verify [154] + o pytest: set `PerSourcePenalties no` when sshd supports it [169] o pytest: silence pytest warnings [261] + o retry.md: clarify that the server can override retry delay time [184] o rtsp: accept a Content-Length body on methods that expected none [115] o rtsp: do not send a learned session id to another origin [10] o runtests: add extra room for mininum number of tests in sliced jobs [201] @@ -181,6 +191,7 @@ This release includes the following bugfixes: o schannel: clear borrowed sslContext on close [15] o schannel: fix `-Wextra-semi-stmt` with clang and extra debug enabled [267] o schannel: fix compiler warning `-Wstrict-aliasing` in ALPN code [243] + o schannel: handle empty tokens during renegotiation [183] o sectrust: add guards for rest of Apple OS flavors [250] o socketpair: fix accept loop [210] o socketpair: use `pipe2()` on Apple OSes (requires SDK 27) [262] @@ -201,10 +212,12 @@ This release includes the following bugfixes: o test2072: enable on Cygwin/MSYS2 [256] o test: restore the old test 1609 as 3271 [162] o tests/http: use @classmethod when appropriate [231] + o tests: clean up Python test code [130] o tests: fix typos in a comment [247] o tests: require valgrind 3+ (2007-08-03) [282] o tftp: verify direction in a few places [101] o tool: (re)set SSL verification in --libcurl output [1] + o tool: add Unicode and long-filename support to `--create-dir` (Windows) [141] o tool: buffer headers before remote filename selection [98] o tool_doswin: recognize superscript numbers as reserved [117] o tool_easysrc: reset the handle between --next operations [90] @@ -212,9 +225,11 @@ This release includes the following bugfixes: o tool_help: guard category[2] access when category is bare "-" [59] o tool_ipfs: keep the path percent-encoded when rewriting to the gateway [138] o tool_operate: clear the retry flag when the output file fails [122] + o tool_util: add Unicode support to `tool_execpath()` [142] o u8_strset, u32_ptrset: fix boundaries handling [131] o uint-bset: skip unused slots when counting o uint-table: add remove-max precaution [165] + o url: check syntax before applying default-protocol [147] o url: match connections without force reuse and candidates [95] o url: ntlm/negotiate, prohibit connection reuse on follows [18] o urlapi: make curl_url_dup() copy the guessed_scheme flag [166] @@ -246,6 +261,7 @@ For all changes ever done in curl: Planned upcoming removals include: + o Common Name support in TLS certificates for OpenSSL o HTTP/2 Server Push o local crypto implementations o NTLM @@ -261,16 +277,18 @@ advice from friends like these: Christian Ullrich, Claude, Dan Fandrich, Daniel Lang, Daniel McCarney, Daniel S. Roche, Daniel Stenberg, David Benjamin, dependabot[bot], Fengxiaoxx on github, Fernando Gallardo, Filippo Tedeschi, Florent Castelli, - Fredrik Blau, Huang Yiheng, Ilias Aberkane, Jorge Rocamora, Joshua Rogers, - JuanCarlosgg on github, KayanoLiam on github, ldm0, Luna Tong, Marcel Raad, - Martin Dukek, Max Dymond, Mihai-Gabriel Marin, minnnjuuu, Mohammed K. Fathy, - Muhamad Arga Reksapati, OSS-Fuzz, Paolo Ganci, Patrick Monnerat, rawsun007, - Ray Satiro, renovate[bot], Rhino, Richard Payne, Ron Kuper, Roshan Ramani, - Rudolf Polzer, Samuel Henrique, Sebastian Raase, Skye Soss, Stanislav Fort, - Stefan Eissing, Stephen Psaradellis, szedenik-adam on github, - Thanniru Sai Teja, Tip ten Brink, Tyler Yankee, Un1q32, Viktor Szakats, + Fredrik Blau, GPT-6.1 Sol, Huang Yiheng, Ilias Aberkane, + jared-m-cohen-civ on github, Johannes Schindelin, Jorge Rocamora, + Joshua Rogers, JuanCarlosgg on github, KayanoLiam on github, ldm0, Luna Tong, + Marcel Raad, Martin Dukek, Max Dymond, Mihai-Gabriel Marin, minnnjuuu, + Mohammed K. Fathy, Muhamad Arga Reksapati, OSS-Fuzz, Paolo Ganci, + Patrick Monnerat, rawsun007, Ray Satiro, Rémi NICOLE, renovate[bot], Rhino, + Richard Payne, Ron Kuper, Roshan Ramani, Rudolf Polzer, Samuel Henrique, + Sebastian Raase, Skye Soss, Stanislav Fort, Stefan Eissing, + Stephen Psaradellis, szedenik-adam on github, Thanniru Sai Teja, + Tip ten Brink, Tyler Yankee, Un1q32, Viktor Szakats, Wonyoung Jung, xhon-pelushi, Yechan Bae, yuwk - (67 contributors) + (72 contributors) References to bug reports and discussions on issues: @@ -403,6 +421,7 @@ References to bug reports and discussions on issues: [127] = https://curl.se/bug/?i=22997 [128] = https://curl.se/bug/?i=22996 [129] = https://curl.se/bug/?i=22993 + [130] = https://curl.se/bug/?i=23290 [131] = https://curl.se/bug/?i=22991 [132] = https://curl.se/bug/?i=22950 [133] = https://curl.se/bug/?i=23086 @@ -412,10 +431,14 @@ References to bug reports and discussions on issues: [138] = https://curl.se/bug/?i=22944 [139] = https://curl.se/bug/?i=23060 [140] = https://curl.se/bug/?i=23083 + [141] = https://curl.se/bug/?i=23289 + [142] = https://curl.se/bug/?i=23287 [143] = https://curl.se/bug/?i=23080 [144] = https://curl.se/bug/?i=23078 [145] = https://curl.se/bug/?i=23068 [146] = https://curl.se/bug/?i=23069 + [147] = https://curl.se/bug/?i=23273 + [148] = https://curl.se/bug/?i=23283 [149] = https://curl.se/bug/?i=23061 [150] = https://curl.se/bug/?i=23071 [151] = https://curl.se/bug/?i=23067 @@ -423,6 +446,7 @@ References to bug reports and discussions on issues: [153] = https://curl.se/bug/?i=23072 [154] = https://curl.se/bug/?i=23064 [155] = https://curl.se/bug/?i=23055 + [156] = https://curl.se/bug/?i=23234 [157] = https://curl.se/bug/?i=23058 [158] = https://curl.se/bug/?i=23056 [159] = https://curl.se/bug/?i=23054 @@ -435,18 +459,27 @@ References to bug reports and discussions on issues: [166] = https://curl.se/bug/?i=23043 [167] = https://curl.se/bug/?i=23045 [168] = https://curl.se/bug/?i=23245 + [169] = https://curl.se/bug/?i=23259 [171] = https://curl.se/bug/?i=23014 + [172] = https://curl.se/bug/?i=23280 [173] = https://curl.se/bug/?i=23037 [174] = https://curl.se/bug/?i=23038 [175] = https://curl.se/bug/?i=23035 + [176] = https://curl.se/bug/?i=23261 [178] = https://curl.se/bug/?i=23030 [179] = https://curl.se/bug/?i=23028 [180] = https://curl.se/bug/?i=23027 [181] = https://curl.se/bug/?i=23026 + [183] = https://github.com/git-for-windows/git/issues/6457 + [184] = https://curl.se/bug/?i=23249 + [185] = https://curl.se/bug/?i=23267 + [187] = https://curl.se/bug/?i=23265 [189] = https://curl.se/bug/?i=23216 [190] = https://curl.se/bug/?i=23215 [191] = https://curl.se/bug/?i=23183 [192] = https://curl.se/bug/?i=23050 + [198] = https://curl.se/bug/?i=23210 + [199] = https://curl.se/bug/?i=23220 [201] = https://curl.se/bug/?i=23211 [203] = https://curl.se/bug/?i=23208 [205] = https://curl.se/bug/?i=23181 From 1c6dc735ea0023993469eab270ac3159a0b713f7 Mon Sep 17 00:00:00 2001 From: Daniel Stenberg Date: Tue, 6 Oct 2026 16:36:30 +0200 Subject: [PATCH 413/481] config2setopts: rename define for maximum cookie header length Ref: #23286 Closes #23288 --- src/config2setopts.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/config2setopts.c b/src/config2setopts.c index b212fb834835..a920a7e4aa33 100644 --- a/src/config2setopts.c +++ b/src/config2setopts.c @@ -541,9 +541,10 @@ static CURLcode cookie_setopts(struct OperationConfig *config, CURL *curl) struct dynbuf cookies; struct curl_slist *cl; - /* The maximum size needs to match MAX_NAME in cookie.h */ -#define MAX_COOKIE_LINE 8200 - curlx_dyn_init(&cookies, MAX_COOKIE_LINE); + /* cap the size of the generated cookie header to avoid servers from going + sour on us */ +#define MAX_REQUEST_COOKIE 8200 + curlx_dyn_init(&cookies, MAX_REQUEST_COOKIE); for(cl = config->cookies; cl; cl = cl->next) { if(cl == config->cookies) result = curlx_dyn_add(&cookies, cl->data); @@ -552,7 +553,7 @@ static CURLcode cookie_setopts(struct OperationConfig *config, CURL *curl) ISBLANK(cl->data[0]) ? "" : " ", cl->data); if(result) { warnf("skipped provided cookie, the cookie header " - "would go over %d bytes", MAX_COOKIE_LINE); + "would go over %d bytes", MAX_REQUEST_COOKIE); return result; } } From d92954301acb24fe596c498ed91d0d30c7d34a15 Mon Sep 17 00:00:00 2001 From: Stefan Eissing Date: Mon, 5 Oct 2026 11:58:37 +0200 Subject: [PATCH 414/481] http2: use stream in userdata Change nghttp2 stream userdata from the transfer to the `h2_stream_ctx` and lookup the transfer at the multi's table. This replaces hash lookups with table lookups which are faster. Closes #23262 --- lib/cf-h2-proxy.c | 2 +- lib/http.c | 4 +- lib/http.h | 2 +- lib/http2.c | 147 ++++++++++++++++++-------------------- lib/vquic/cf-ngtcp2-cmn.h | 2 +- lib/vquic/cf-quiche.c | 2 +- 6 files changed, 75 insertions(+), 84 deletions(-) diff --git a/lib/cf-h2-proxy.c b/lib/cf-h2-proxy.c index b10e785ea942..08a1dd7e1913 100644 --- a/lib/cf-h2-proxy.c +++ b/lib/cf-h2-proxy.c @@ -580,7 +580,7 @@ static int proxy_h2_on_header(nghttp2_session *session, if(namelen == CURL_CSTRLEN(HTTP_PSEUDO_STATUS) && !memcmp(HTTP_PSEUDO_STATUS, name, namelen)) { - int http_status; + int32_t http_status; struct http_resp *resp; /* status: always comes first, we might get more than one response, diff --git a/lib/http.c b/lib/http.c index 21dbc120a641..016e42c3bddf 100644 --- a/lib/http.c +++ b/lib/http.c @@ -4737,10 +4737,10 @@ CURLcode Curl_http_write_resp(struct Curl_easy *data, } /* Decode HTTP status code string. */ -CURLcode Curl_http_decode_status(int *pstatus, const char *s, size_t len) +CURLcode Curl_http_decode_status(int32_t *pstatus, const char *s, size_t len) { CURLcode result = CURLE_BAD_FUNCTION_ARGUMENT; - int status = 0; + int32_t status = 0; int i; if(len != 3) diff --git a/lib/http.h b/lib/http.h index 83b4cc6c6414..2756378092df 100644 --- a/lib/http.h +++ b/lib/http.h @@ -197,7 +197,7 @@ CURLcode Curl_http_output_auth(struct Curl_easy *data, bool is_connect); /* Decode HTTP status code string. */ -CURLcode Curl_http_decode_status(int *pstatus, const char *s, size_t len); +CURLcode Curl_http_decode_status(int32_t *pstatus, const char *s, size_t len); /** * All about a core HTTP request, excluding body and trailers diff --git a/lib/http2.c b/lib/http2.c index a8ff15fb6771..9010179e57be 100644 --- a/lib/http2.c +++ b/lib/http2.c @@ -125,17 +125,19 @@ struct h2_stream_ctx { struct h1_req_parser h1; /* parsing the request */ struct dynhds resp_trailers; /* response trailer fields */ size_t resp_hds_len; /* amount of response header bytes in recvbuf */ + curl_off_t xfer_id; /* transfer `id` owning this stream */ curl_off_t nrcvd_data; /* number of DATA bytes received */ char **push_headers; /* allocated array */ size_t push_headers_used; /* number of entries filled in */ size_t push_headers_alloc; /* number of entries allocated */ - int status_code; /* HTTP response status code */ + int32_t id; /* HTTP/2 protocol identifier for stream */ + uint32_t mid; /* transfer `mid` owning this stream */ uint32_t error; /* stream error code */ CURLcode xfer_result; /* Result of writing out response */ int32_t local_window_size; /* the local recv window size */ - int32_t id; /* HTTP/2 protocol identifier for stream */ + int32_t status_code; /* HTTP response status code */ BIT(resp_hds_complete); /* we have a complete, final response */ BIT(closed); /* TRUE on stream close */ BIT(reset); /* TRUE on stream reset */ @@ -257,16 +259,18 @@ static CURLcode cf_h2_update_settings(struct cf_h2_ctx *ctx, ((struct h2_stream_ctx *)( \ (data) ? Curl_u32_ptrset_get(&(ctx)->streams, (data)->mid) : NULL)) -static struct h2_stream_ctx *h2_stream_ctx_create(struct cf_h2_ctx *ctx) +static struct h2_stream_ctx *h2_stream_ctx_create(struct cf_h2_ctx *ctx, + struct Curl_easy *data) { struct h2_stream_ctx *stream; - (void)ctx; stream = curlx_calloc(1, sizeof(*stream)); if(!stream) return NULL; stream->id = -1; + stream->xfer_id = data->id; + stream->mid = data->mid; Curl_bufq_initp(&stream->sendbuf, &ctx->stream_bufcp, H2_STREAM_SEND_CHUNKS, BUFQ_OPT_NONE); Curl_h1_req_parse_init(&stream->h1, H1_PARSE_DEFAULT_MAX_LINE_LEN); @@ -282,6 +286,28 @@ static struct h2_stream_ctx *h2_stream_ctx_create(struct cf_h2_ctx *ctx) return stream; } +static struct Curl_easy *cf_h2_get_stream_xfer(struct Curl_cfilter *cf, + int32_t stream_id, + struct h2_stream_ctx **pstream) +{ + struct cf_h2_ctx *ctx = cf->ctx; + struct h2_stream_ctx *stream = + nghttp2_session_get_stream_user_data(ctx->h2, stream_id); + if(pstream) + *pstream = stream; + if(stream && (stream->mid != UINT32_MAX)) { + struct Curl_easy *data, *call_data = CF_DATA_CURRENT(cf); + if(!call_data || !call_data->multi) { + DEBUGASSERT(0); + return NULL; + } + data = Curl_multi_get_easy(call_data->multi, stream->mid); + if(data && (data->id == stream->xfer_id)) + return data; + } + return NULL; +} + #ifdef NGHTTP2_HAS_SET_LOCAL_WINDOW_SIZE static int32_t cf_h2_get_desired_local_win(struct Curl_cfilter *cf, struct Curl_easy *data) @@ -378,7 +404,7 @@ static CURLcode http2_data_setup(struct Curl_cfilter *cf, return CURLE_OK; } - stream = h2_stream_ctx_create(ctx); + stream = h2_stream_ctx_create(ctx, data); if(!stream) return CURLE_OUT_OF_MEMORY; @@ -886,7 +912,7 @@ static int push_promise(struct Curl_cfilter *cf, newhandle->mid); rv = nghttp2_session_set_stream_user_data(ctx->h2, newstream->id, - newhandle); + newstream); if(rv) { infof(data, "failed to set user_data for stream %d", newstream->id); @@ -956,10 +982,10 @@ static void h2_xfer_write_resp(struct Curl_cfilter *cf, static CURLcode on_stream_frame(struct Curl_cfilter *cf, struct Curl_easy *data, + struct h2_stream_ctx *stream, const nghttp2_frame *frame) { struct cf_h2_ctx *ctx = cf->ctx; - struct h2_stream_ctx *stream = H2_STREAM_CTX(ctx, data); int32_t stream_id = frame->hd.stream_id; int rv; @@ -1172,6 +1198,7 @@ static int on_frame_recv(nghttp2_session *session, const nghttp2_frame *frame, struct Curl_cfilter *cf = userp; struct cf_h2_ctx *ctx = cf->ctx; struct Curl_easy *data = CF_DATA_CURRENT(cf), *data_s; + struct h2_stream_ctx *stream; int32_t stream_id = frame->hd.stream_id; DEBUGASSERT(data); @@ -1232,13 +1259,14 @@ static int on_frame_recv(nghttp2_session *session, const nghttp2_frame *frame, return 0; } - data_s = nghttp2_session_get_stream_user_data(session, stream_id); + data_s = cf_h2_get_stream_xfer(cf, stream_id, &stream); if(!data_s) { CURL_TRC_CF(data, cf, "[%d] No Curl_easy associated", stream_id); return 0; } - return on_stream_frame(cf, data_s, frame) ? NGHTTP2_ERR_CALLBACK_FAILURE : 0; + return on_stream_frame(cf, data_s, stream, frame) ? + NGHTTP2_ERR_CALLBACK_FAILURE : 0; } static int cf_h2_on_invalid_frame_recv(nghttp2_session *session, @@ -1248,11 +1276,12 @@ static int cf_h2_on_invalid_frame_recv(nghttp2_session *session, struct Curl_cfilter *cf = userp; struct cf_h2_ctx *ctx = cf->ctx; struct Curl_easy *data; + struct h2_stream_ctx *stream; int32_t stream_id = frame->hd.stream_id; - data = nghttp2_session_get_stream_user_data(session, stream_id); + (void)session; + data = cf_h2_get_stream_xfer(cf, stream_id, &stream); if(data) { - struct h2_stream_ctx *stream; #ifdef CURLVERBOSE char buffer[256]; int len; @@ -1261,7 +1290,6 @@ static int cf_h2_on_invalid_frame_recv(nghttp2_session *session, failf(data, "[HTTP2] [%d] received invalid frame: %s, error %d: %s", stream_id, buffer, ngerr, nghttp2_strerror(ngerr)); #endif /* CURLVERBOSE */ - stream = H2_STREAM_CTX(ctx, data); if(stream) { nghttp2_submit_rst_stream(ctx->h2, NGHTTP2_FLAG_NONE, stream->id, NGHTTP2_STREAM_CLOSED); @@ -1288,7 +1316,7 @@ static int on_data_chunk_recv(nghttp2_session *session, uint8_t flags, DEBUGASSERT(CF_DATA_CURRENT(cf)); /* get the stream from the hash based on Stream ID */ - data_s = nghttp2_session_get_stream_user_data(session, stream_id); + data_s = cf_h2_get_stream_xfer(cf, stream_id, &stream); if(!data_s) { /* Receiving a Stream ID not in the hash should not happen - unless we have aborted a transfer artificially and there were more data @@ -1299,7 +1327,6 @@ static int on_data_chunk_recv(nghttp2_session *session, uint8_t flags, return 0; } - stream = H2_STREAM_CTX(ctx, data_s); if(!stream) return NGHTTP2_ERR_CALLBACK_FAILURE; @@ -1316,14 +1343,13 @@ static int on_stream_close(nghttp2_session *session, int32_t stream_id, struct Curl_cfilter *cf = userp; struct cf_h2_ctx *ctx = cf->ctx; struct Curl_easy *data_s, *call_data = CF_DATA_CURRENT(cf); - struct h2_stream_ctx *stream; + struct h2_stream_ctx *stream = NULL; int rv; (void)session; DEBUGASSERT(call_data); /* stream id 0 is the connection, do not look there for streams. */ - data_s = stream_id ? - nghttp2_session_get_stream_user_data(session, stream_id) : NULL; + data_s = stream_id ? cf_h2_get_stream_xfer(cf, stream_id, &stream) : NULL; if(!data_s) { CURL_TRC_CF(call_data, cf, "[%d] on_stream_close, no easy set on stream", stream_id); @@ -1335,10 +1361,9 @@ static int on_stream_close(nghttp2_session *session, int32_t stream_id, * trigger DONE or DETACH events as it must. */ CURL_TRC_CF(call_data, cf, "[%d] on_stream_close, not a GOOD easy on stream", stream_id); - (void)nghttp2_session_set_stream_user_data(session, stream_id, 0); + (void)nghttp2_session_set_stream_user_data(session, stream_id, NULL); return NGHTTP2_ERR_CALLBACK_FAILURE; } - stream = H2_STREAM_CTX(ctx, data_s); if(!stream) { CURL_TRC_CF(data_s, cf, "[%d] on_stream_close, GOOD easy but no stream", stream_id); @@ -1365,8 +1390,8 @@ static int on_stream_close(nghttp2_session *session, int32_t stream_id, CURL_TRC_CF(data_s, cf, "[%d] CLOSED", stream_id); Curl_multi_mark_dirty(data_s); - /* remove `data_s` from the nghttp2 stream */ - rv = nghttp2_session_set_stream_user_data(session, stream_id, 0); + /* remove `stream_ctx` from the nghttp2 stream */ + rv = nghttp2_session_set_stream_user_data(session, stream_id, NULL); if(rv) { infof(data_s, "http/2: failed to clear user_data for stream %d", stream_id); @@ -1375,32 +1400,6 @@ static int on_stream_close(nghttp2_session *session, int32_t stream_id, return 0; } -static int on_begin_headers(nghttp2_session *session, - const nghttp2_frame *frame, void *userp) -{ - struct Curl_cfilter *cf = userp; - struct cf_h2_ctx *ctx = cf->ctx; - struct h2_stream_ctx *stream; - struct Curl_easy *data_s = NULL; - - (void)cf; - data_s = nghttp2_session_get_stream_user_data(session, frame->hd.stream_id); - if(!data_s) { - return 0; - } - - if(frame->hd.type != NGHTTP2_HEADERS) { - return 0; - } - - stream = H2_STREAM_CTX(ctx, data_s); - if(!stream || !stream->bodystarted) { - return 0; - } - - return 0; -} - static void cf_h2_header_error(struct Curl_cfilter *cf, struct Curl_easy *data, struct h2_stream_ctx *stream, @@ -1436,13 +1435,12 @@ static int on_header(nghttp2_session *session, const nghttp2_frame *frame, DEBUGASSERT(stream_id); /* should never be a zero stream ID here */ /* get the stream from the hash based on Stream ID */ - data = nghttp2_session_get_stream_user_data(session, stream_id); + data = cf_h2_get_stream_xfer(cf, stream_id, &stream); if(!GOOD_EASY_HANDLE(data)) /* Receiving a Stream ID not in the hash should not happen, this is an internal error more than anything else! */ return NGHTTP2_ERR_CALLBACK_FAILURE; - stream = H2_STREAM_CTX(ctx, data); if(!stream) { failf(data, "Internal NULL stream"); return NGHTTP2_ERR_CALLBACK_FAILURE; @@ -1605,7 +1603,6 @@ static ssize_t req_body_read_callback(nghttp2_session *session, void *userp) { struct Curl_cfilter *cf = userp; - struct cf_h2_ctx *ctx = cf->ctx; struct Curl_easy *data_s; struct h2_stream_ctx *stream = NULL; CURLcode result; @@ -1613,22 +1610,18 @@ static ssize_t req_body_read_callback(nghttp2_session *session, size_t n; (void)source; - (void)cf; + (void)session; if(!stream_id) return NGHTTP2_ERR_INVALID_ARGUMENT; /* get the stream from the hash based on Stream ID, stream ID zero is for connection-oriented stuff */ - data_s = nghttp2_session_get_stream_user_data(session, stream_id); - if(!data_s) + data_s = cf_h2_get_stream_xfer(cf, stream_id, &stream); + if(!data_s || !stream) /* Receiving a Stream ID not in the hash should not happen, this is an internal error more than anything else! */ return NGHTTP2_ERR_CALLBACK_FAILURE; - stream = H2_STREAM_CTX(ctx, data_s); - if(!stream) - return NGHTTP2_ERR_CALLBACK_FAILURE; - result = Curl_bufq_read(&stream->sendbuf, buf, length, &n); if(result) { if(result != CURLE_AGAIN) @@ -1818,21 +1811,23 @@ static CURLcode h2_progress_egress(struct Curl_cfilter *cf, struct Curl_easy *data) { struct cf_h2_ctx *ctx = cf->ctx; - struct h2_stream_ctx *stream = H2_STREAM_CTX(ctx, data); int rv = 0; - if(stream && stream->id > 0 && - (sweight_wanted(data) != sweight_in_effect(data))) { - /* send new weight and/or dependency */ - nghttp2_priority_spec pri_spec; - - h2_pri_spec(data, &pri_spec); - CURL_TRC_CF(data, cf, "[%d] Queuing PRIORITY", stream->id); - DEBUGASSERT(stream->id != -1); - rv = nghttp2_submit_priority(ctx->h2, NGHTTP2_FLAG_NONE, - stream->id, &pri_spec); - if(rv) - goto out; + if(sweight_wanted(data) != sweight_in_effect(data)) { + struct h2_stream_ctx *stream = H2_STREAM_CTX(ctx, data); + if(stream && stream->id > 0 && + (sweight_wanted(data) != sweight_in_effect(data))) { + /* send new weight and/or dependency */ + nghttp2_priority_spec pri_spec; + + h2_pri_spec(data, &pri_spec); + CURL_TRC_CF(data, cf, "[%d] Queuing PRIORITY", stream->id); + DEBUGASSERT(stream->id != -1); + rv = nghttp2_submit_priority(ctx->h2, NGHTTP2_FLAG_NONE, + stream->id, &pri_spec); + if(rv) + goto out; + } } ctx->nw_out_blocked = 0; @@ -1893,7 +1888,6 @@ static CURLcode h2_progress_ingress(struct Curl_cfilter *cf, size_t data_max_bytes) { struct cf_h2_ctx *ctx = cf->ctx; - struct h2_stream_ctx *stream; CURLcode result = CURLE_OK; size_t nread; @@ -1918,7 +1912,7 @@ static CURLcode h2_progress_ingress(struct Curl_cfilter *cf, * it is time to stop due to connection close or us not processing * all network input */ while(!ctx->conn_closed && Curl_bufq_is_empty(&ctx->inbufq)) { - stream = H2_STREAM_CTX(ctx, data); + struct h2_stream_ctx *stream = H2_STREAM_CTX(ctx, data); if(stream && (stream->closed || !data_max_bytes)) { /* We would like to abort here and stop processing, so that the transfer * loop can handle the data/close here. This may leave data in @@ -2157,11 +2151,11 @@ static CURLcode h2_submit(struct h2_stream_ctx **pstream, data_prd.read_callback = req_body_read_callback; data_prd.source.ptr = NULL; stream_id = nghttp2_submit_request(ctx->h2, &pri_spec, nva, nheader, - &data_prd, data); + &data_prd, stream); break; default: stream_id = nghttp2_submit_request(ctx->h2, &pri_spec, nva, nheader, - NULL, data); + NULL, stream); } if(stream_id < 0) { @@ -2424,8 +2418,6 @@ static CURLcode cf_h2_ctx_open(struct Curl_cfilter *cf, nghttp2_session_callbacks_set_on_data_chunk_recv_callback( cbs, on_data_chunk_recv); nghttp2_session_callbacks_set_on_stream_close_callback(cbs, on_stream_close); - nghttp2_session_callbacks_set_on_begin_headers_callback( - cbs, on_begin_headers); nghttp2_session_callbacks_set_on_header_callback(cbs, on_header); #ifdef CURLVERBOSE nghttp2_session_callbacks_set_error_callback(cbs, error_callback); @@ -2473,8 +2465,7 @@ static CURLcode cf_h2_ctx_open(struct Curl_cfilter *cf, goto out; } - rc = nghttp2_session_set_stream_user_data(ctx->h2, stream->id, - data); + rc = nghttp2_session_set_stream_user_data(ctx->h2, stream->id, stream); if(rc) { infof(data, "http/2: failed to set user_data for stream %d", stream->id); diff --git a/lib/vquic/cf-ngtcp2-cmn.h b/lib/vquic/cf-ngtcp2-cmn.h index 7d182ac0e696..2ead50ea1526 100644 --- a/lib/vquic/cf-ngtcp2-cmn.h +++ b/lib/vquic/cf-ngtcp2-cmn.h @@ -176,7 +176,7 @@ struct h3_stream_ctx { size_t tx_in_flight_size; /* sendbuf data "in flight" */ size_t tx_in_flight_ideal; /* ideal amount of un-acked send data */ uint32_t mid; /* `mid` of transfer owning this stream */ - int status_code; /* HTTP status code */ + int32_t status_code; /* HTTP status code */ CURLcode xfer_result; /* result from xfer_resp_write(_hd) */ BIT(resp_hds_complete); /* we have a complete, final response */ BIT(closed); /* TRUE on stream close */ diff --git a/lib/vquic/cf-quiche.c b/lib/vquic/cf-quiche.c index 441638601f10..303bbebd1081 100644 --- a/lib/vquic/cf-quiche.c +++ b/lib/vquic/cf-quiche.c @@ -176,7 +176,7 @@ struct h3_stream_ctx { uint64_t id; /* HTTP/3 protocol stream identifier */ struct h1_req_parser h1; /* h1 request parsing */ uint64_t error3; /* HTTP/3 stream error code */ - int status_code; /* HTTP status code */ + int32_t status_code; /* HTTP status code */ CURLcode xfer_result; /* result from cf_quiche_write_(hd/body) */ BIT(opened); /* TRUE after stream has been opened */ BIT(closed); /* TRUE on stream close */ From e08d30638e59d0a19c881a856ea1f59693680dc5 Mon Sep 17 00:00:00 2001 From: Stefan Eissing Date: Wed, 30 Sep 2026 14:46:14 +0200 Subject: [PATCH 415/481] websocket: error on EOS with incomplete frame When the connection is closed, a websocket client writer that sees the EOS in the middle of a frame errors out. reported-by: Joshua Rogers Closes https://github.com/curl/curl/pull/23188 --- lib/ws.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/ws.c b/lib/ws.c index 305991f96a71..26023245909a 100644 --- a/lib/ws.c +++ b/lib/ws.c @@ -759,18 +759,18 @@ static CURLcode ws_cw_write(struct Curl_easy *data, } } - if((type & CLIENTWRITE_EOS) && !Curl_bufq_is_empty(&ctx->buf)) { - failf(data, "[WS] decode ending with %zu frame bytes remaining", - Curl_bufq_len(&ctx->buf)); - result = CURLE_RECV_ERROR; - } - out: if(!result) { result = ws_flush(data, ws, Curl_api_is_in_callback(data)); if(result == CURLE_AGAIN) result = CURLE_OK; } + + if(!result && (type & CLIENTWRITE_EOS) && + (!Curl_bufq_is_empty(&ctx->buf) || (ws->dec.state != WS_DEC_INIT))) { + failf(data, "[WS] decode ended with incomplete frame"); + result = CURLE_RECV_ERROR; + } return result; } From 4b03329952042499bb27a5ce9771d4bdc1f56903 Mon Sep 17 00:00:00 2001 From: Stefan Eissing Date: Thu, 1 Oct 2026 12:01:46 +0200 Subject: [PATCH 416/481] easy: change event pollset construction - Switch from pollfd to curl_pollfds. This change also eliminates a reported problem that the max `pollfd` count was not checked. reported-by: Joshua Rogers Closes https://github.com/curl/curl/pull/23212 --- lib/easy.c | 79 ++++++++++++++++++++++++++---------------------------- 1 file changed, 38 insertions(+), 41 deletions(-) diff --git a/lib/easy.c b/lib/easy.c index fc0041e74c4d..9d467f4c4be3 100644 --- a/lib/easy.c +++ b/lib/easy.c @@ -555,48 +555,37 @@ static void events_setup(struct Curl_multi *multi, struct events *ev) curl_multi_setopt(multi, CURLMOPT_SOCKETDATA, ev); } -/* populate_fds() - * - * populate the fds[] array - */ -static unsigned int populate_fds(struct pollfd *fds, struct events *ev) +static CURLcode populate_cpfds(struct curl_pollfds *cpfds, struct events *ev) { - unsigned int numfds = 0; - struct pollfd *f; struct socketmonitor *m; + CURLcode result = CURLE_OK; - f = &fds[0]; - for(m = ev->list; m; m = m->next) { - f->fd = m->socket.fd; - f->events = m->socket.events; - f->revents = 0; + Curl_pollfds_reset(cpfds); + for(m = ev->list; m && !result; m = m->next) { + result = Curl_pollfds_add_sock(cpfds, m->socket.fd, m->socket.events); #if DEBUG_EV_POLL - curl_mfprintf(stderr, "poll() %d check socket %d\n", numfds, f->fd); + if(!result) + curl_mfprintf(stderr, "poll() %d check socket %d\n", + cpfds->n - 1, cpfds->pfds[cpfds->n - 1].fd); #endif - f++; - numfds++; } - return numfds; + return result; } -/* poll_fds() - * - * poll the fds[] array - */ -static CURLcode poll_fds(struct events *ev, - struct pollfd *fds, - const unsigned int numfds, - int *pollrc) +static CURLcode poll_cpfds(struct events *ev, + struct curl_pollfds *cpfds, + int *pollrc) { - if(numfds) { + if(cpfds->n) { /* wait for activity or timeout */ #if DEBUG_EV_POLL - curl_mfprintf(stderr, "poll(numfds=%u, timeout=%ldms)\n", numfds, ev->ms); + curl_mfprintf(stderr, "poll(numfds=%u, timeout=%ldms)\n", + cpfds->n, ev->ms); #endif - *pollrc = Curl_poll(fds, numfds, ev->ms); + *pollrc = Curl_poll(cpfds->pfds, cpfds->n, ev->ms); #if DEBUG_EV_POLL curl_mfprintf(stderr, "poll(numfds=%u, timeout=%ldms) -> %d\n", - numfds, ev->ms, *pollrc); + cpfds->n, ev->ms, *pollrc); #endif if(*pollrc < 0) return CURLE_UNRECOVERABLE_POLL; @@ -618,30 +607,35 @@ static CURLcode poll_fds(struct events *ev, */ static CURLcode wait_or_timeout(struct Curl_multi *multi, struct events *ev) { + struct pollfd fds_on_stack[8]; + struct curl_pollfds cpfds; bool done = FALSE; CURLMcode mresult = CURLM_OK; CURLcode result = CURLE_OK; + Curl_pollfds_init(&cpfds, fds_on_stack, CURL_ARRAYSIZE(fds_on_stack)); while(!done) { CURLMsg *msg; - struct pollfd fds[4]; int pollrc; struct curltime start; - const unsigned int numfds = populate_fds(fds, ev); + + result = populate_cpfds(&cpfds, ev); + if(result) + goto out; /* get the time stamp to use to figure out how long poll takes */ curlx_pnow(&start); - result = poll_fds(ev, fds, numfds, &pollrc); + result = poll_cpfds(ev, &cpfds, &pollrc); if(result) - return result; + goto out; ev->msbump = FALSE; /* reset here */ if(!pollrc) { /* timeout! */ ev->ms = 0; -#if 0 +#if DEBUG_EV_POLL curl_mfprintf(stderr, "call curl_multi_socket_action(TIMEOUT)\n"); #endif mresult = curl_multi_socket_action(multi, CURL_SOCKET_TIMEOUT, 0, @@ -651,15 +645,15 @@ static CURLcode wait_or_timeout(struct Curl_multi *multi, struct events *ev) /* here pollrc is > 0 */ /* loop over the monitored sockets to see which ones had activity */ unsigned int i; - for(i = 0; i < numfds; i++) { - if(fds[i].revents) { + for(i = 0; i < cpfds.n; i++) { + if(cpfds.pfds[i].revents) { /* socket activity, tell libcurl */ - int act = poll2cselect(fds[i].revents); /* convert */ + int act = poll2cselect(cpfds.pfds[i].revents); /* convert */ /* sending infof "randomly" to the first easy handle */ infof(multi->admin, "call curl_multi_socket_action(socket " - "%" FMT_SOCKET_T ")", (curl_socket_t)fds[i].fd); - mresult = curl_multi_socket_action(multi, fds[i].fd, act, + "%" FMT_SOCKET_T ")", (curl_socket_t)cpfds.pfds[i].fd); + mresult = curl_multi_socket_action(multi, cpfds.pfds[i].fd, act, &ev->running_handles); } } @@ -682,8 +676,10 @@ static CURLcode wait_or_timeout(struct Curl_multi *multi, struct events *ev) } } - if(mresult) - return CURLE_URL_MALFORMAT; + if(mresult) { + result = CURLE_URL_MALFORMAT; + goto out; + } /* we do not really care about the "msgs_in_queue" value returned in the second argument */ @@ -693,7 +689,8 @@ static CURLcode wait_or_timeout(struct Curl_multi *multi, struct events *ev) done = TRUE; } } - +out: + Curl_pollfds_cleanup(&cpfds); return result; } From 36a582d2c02fef5e6122179ee33d3130f53b3e5b Mon Sep 17 00:00:00 2001 From: Max Dymond Date: Tue, 6 Oct 2026 22:31:54 +0100 Subject: [PATCH 417/481] http: reject empty header names A custom proxy header can have a non-empty name that becomes empty after trimming. An HTTP/1 header line can also start with a colon. Reject these inputs before they reach Curl_dynhds_add(), which requires a non-empty name. Keep custom proxy headers consistent with existing behavior by ignoring the malformed entry. Return CURLE_BAD_FUNCTION_ARGUMENT from the HTTP/1 parser. Add unit and proxy CONNECT regression coverage. Follow-up to 1c9f1f4c3f88 Ref: curl/curl-fuzzer#409 Closes https://github.com/curl/curl/pull/23292 --- lib/dynhds.c | 2 +- lib/http_proxy.c | 2 ++ tests/data/test1428 | 2 +- tests/unit/unit2602.c | 7 +++++++ 4 files changed, 11 insertions(+), 2 deletions(-) diff --git a/lib/dynhds.c b/lib/dynhds.c index 08d0f8289512..c3aad4b503f8 100644 --- a/lib/dynhds.c +++ b/lib/dynhds.c @@ -204,7 +204,7 @@ CURLcode Curl_dynhds_h1_add_line(struct dynhds *dynhds, return CURLE_OK; p = memchr(line, ':', line_len); - if(!p) + if(!p || p == line) return CURLE_BAD_FUNCTION_ARGUMENT; name = line; namelen = p - line; diff --git a/lib/http_proxy.c b/lib/http_proxy.c index 78b4a2e7bc4b..ad79a477b74b 100644 --- a/lib/http_proxy.c +++ b/lib/http_proxy.c @@ -132,6 +132,8 @@ static CURLcode dynhds_add_custom(struct Curl_easy *data, /* trim surrounding whitespace so a padded field name (e.g. `Authorization :`) cannot slip past the Authorization/Cookie check */ curlx_str_trimblanks(&name); + if(!curlx_strlen(&name)) + continue; if(data->state.http_host && /* a Host: header was sent already, do not pass on any custom Host: header as that will produce *two* in the same request! */ diff --git a/tests/data/test1428 b/tests/data/test1428 index 858d2cd9f1d5..b548a54d4b46 100644 --- a/tests/data/test1428 +++ b/tests/data/test1428 @@ -49,7 +49,7 @@ http-proxy HTTP over proxy-tunnel with --proxy-header and --header -http://test.%TESTNUMBER:%HTTPPORT/we/want/that/page/%TESTNUMBER -p -x %HOSTIP:%PROXYPORT --user 'iam:my:;self' --header "header-type: server" --proxy-header "header-type: proxy" +http://test.%TESTNUMBER:%HTTPPORT/we/want/that/page/%TESTNUMBER -p -x %HOSTIP:%PROXYPORT --user 'iam:my:;self' --header "header-type: server" --proxy-header "header-type: proxy" --proxy-header " : ignored" proxy diff --git a/tests/unit/unit2602.c b/tests/unit/unit2602.c index c00a3f3d5c71..22f158d6b831 100644 --- a/tests/unit/unit2602.c +++ b/tests/unit/unit2602.c @@ -34,6 +34,7 @@ static CURLcode test_unit2602(const char *arg) struct dynbuf dbuf; CURLcode result; size_t i; + size_t nentries; /* add 1 more header than allowed */ Curl_dynhds_init(&hds, 2, 128); @@ -103,6 +104,12 @@ static CURLcode test_unit2602(const char *arg) result = Curl_dynhds_h1_cadd_line(&hds, "blablabla thingies"); fail_unless(result, "add should have failed"); + nentries = Curl_dynhds_count(&hds); + result = Curl_dynhds_h1_cadd_line(&hds, ": thingies"); + fail_unless(result == CURLE_BAD_FUNCTION_ARGUMENT, + "empty name should have failed"); + fail_unless(Curl_dynhds_count(&hds) == nentries, + "empty name should not have been added"); if(!result) { fail_unless(dynhds_ccount_name(&hds, "bLABlaBlA") == 0, "should"); fail_if(Curl_dynhds_cadd(&hds, "Bla-Bla", "thingies"), "add failed"); From 6c7468718ef976172fdd983c1ac3ae128e93bcbc Mon Sep 17 00:00:00 2001 From: Jorge Rocamora <33847633+aeroyorch@users.noreply.github.com> Date: Mon, 21 Sep 2026 22:40:27 +0200 Subject: [PATCH 418/481] connect: do not busy-loop in blocking connect without a timeout - During blocking connect treat a transfer/connection timeout of 0 as timeout of 1000 for polling. Prior to this change if no timeout was set then 0 was passed as the timeout time to poll which caused a busy loop since it returned immediately. Closes https://github.com/curl/curl/pull/23024 --- lib/connect.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/connect.c b/lib/connect.c index 82bca3ece550..603dfc73596a 100644 --- a/lib/connect.c +++ b/lib/connect.c @@ -345,7 +345,8 @@ CURLcode Curl_conn_connect(struct Curl_easy *data, goto out; rc = Curl_poll(cpfds.pfds, cpfds.n, - CURLMIN(timeout_ms, (cpfds.n ? 1000 : 10))); + CURLMIN(timeout_ms ? timeout_ms : 1000, + (cpfds.n ? 1000 : 10))); CURL_TRC_CF(data, cf, "Curl_conn_connect(block=1), Curl_poll() -> %d", rc); if(rc < 0) { From 11d0b202136debd81c9cc8dae49f3396670d0b85 Mon Sep 17 00:00:00 2001 From: Stefan Eissing Date: Wed, 7 Oct 2026 10:06:39 +0200 Subject: [PATCH 419/481] multi: is_finished() fix When the progress callback errors on a not comppleted transfer, the transfer state entered COMPLETED right away and not DONE. This led to cleanup actions on the transfer/connection to not run. Only go to MSTATE_DONE in this case. Reported-by: Anthropic Closes #23295 --- lib/multi.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/multi.c b/lib/multi.c index 8f8d0b525fba..a79a8a9d267f 100644 --- a/lib/multi.c +++ b/lib/multi.c @@ -125,6 +125,8 @@ static void mstate_enter_completed(struct Curl_easy *data, Curl_pgrsTime(data, TIMER_POSTRANSFER); Curl_pgrsTime(data, TIMER_STARTTRANSFER); } + /* multi_done() should have run and detached any connection already */ + DEBUGASSERT(!data->conn); Curl_pgrsCompleted(data); if(from_state < MSTATE_DONE) CURLM_NTFY(data, CURLMNOTIFY_EASY_DONE); @@ -2400,9 +2402,9 @@ static CURLcode is_finished(struct Curl_multi *multi, connection */ streamclose(data->conn); - /* if not yet in DONE state, go there, otherwise COMPLETED */ - multistate(data, (data->mstate < MSTATE_DONE) ? - MSTATE_DONE : MSTATE_COMPLETED); + /* if not yet in DONE state, go there. */ + if(data->mstate < MSTATE_DONE) + multistate(data, MSTATE_DONE); return result; } } From ffc18980959ab97aece40c9435cf57f87dca940b Mon Sep 17 00:00:00 2001 From: Viktor Szakats Date: Wed, 7 Oct 2026 11:31:28 +0200 Subject: [PATCH 420/481] pytest: adjust threshold in test_20_11_crazy_pings to better pass on arm64 Seen with GHA/http3-linux BoringSSL since moving from x64 to arm64: ``` > assert (rss1 * 1.2) > rss2, 'bad memory increase' E AssertionError: bad memory increase E assert (10.15625 * 1.2) > 12.2734375 ../../tests/http/test_20_websockets.py:330: AssertionError ``` Ref: https://github.com/curl/curl/actions/runs/37599901852/job/112721784104?pr=23294#step:32:2093 ``` > assert (rss1 * 1.2) > rss2, 'bad memory increase' E AssertionError: bad memory increase E assert (10.16015625 * 1.2) > 12.390625 ../../tests/http/test_20_websockets.py:330: AssertionError ``` Ref: https://github.com/curl/curl/actions/runs/37555944489/job/112582218973#step:32:2093 Follow-up to 82b289c9527890a0766b55be2214b3749283307a #22574 Follow-up to 849317ff5c5a5e13f50ec3d001e46ddffa77d8a4 #21911 Closes #23298 --- tests/http/test_20_websockets.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/http/test_20_websockets.py b/tests/http/test_20_websockets.py index 9ea9edc5d56a..a0f32585d82e 100644 --- a/tests/http/test_20_websockets.py +++ b/tests/http/test_20_websockets.py @@ -327,7 +327,7 @@ def srv(): assert r.exit_code in [55, 56], f'{r.dump_logs()}' # SEND/RECV_ERROR assert r.profile, f'{r}' rss2 = r.profile.stats['rss-max'] / (1024 * 1024) - assert (rss1 * 1.2) > rss2, 'bad memory increase' + assert (rss1 * 1.23) > rss2, 'bad memory increase' # test small frames delivery when pausing def test_20_12_pause_frames_small(self, env: Env, ws_4frames): From 2f114dcdf1687781459447edce08feb3947e5010 Mon Sep 17 00:00:00 2001 From: Viktor Szakats Date: Wed, 7 Oct 2026 19:41:34 +0200 Subject: [PATCH 421/481] appveyor: sync debug option logic with GHA Pass `-DENABLE_DEBUG=ON` on a per-job basis, instead of passing it by default and disabling it for Release jobs. To simplify and to allow building a non-debug-enabled curl in both Release and Debug mode. Closes #23307 --- appveyor.sh | 1 - appveyor.yml | 20 ++++++++++---------- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/appveyor.sh b/appveyor.sh index 779553a38d3e..5c43e7c17947 100644 --- a/appveyor.sh +++ b/appveyor.sh @@ -73,7 +73,6 @@ if [ -n "${CMAKE_GENERATOR:-}" ]; then [[ "${CMAKE_GENERATE:-}" = *'-DCURL_USE_OPENSSL=ON'* ]] && options+=" -DOPENSSL_ROOT_DIR=${openssl_root_win}" # shellcheck disable=SC2086 time cmake -B "_bld${_chkprefill}" \ - -DENABLE_DEBUG=ON \ -DCMAKE_UNITY_BUILD=ON -DCURL_WERROR=ON \ -DCMAKE_VS_GLOBALS=TrackFileAccess=false \ -DCURL_STATIC_CRT=ON \ diff --git a/appveyor.yml b/appveyor.yml index 41cc3d5139b8..1ea548febf3b 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -39,67 +39,67 @@ environment: - job_name: 'CM VS2022, Release, x64, OpenSSL 3.5, Shared, Build-tests' APPVEYOR_BUILD_WORKER_IMAGE: 'Visual Studio 2022' CMAKE_GENERATOR: 'Visual Studio 17 2022' - CMAKE_GENERATE: '-A x64 -DENABLE_DEBUG=OFF -DCURL_USE_SCHANNEL=OFF -DCURL_USE_OPENSSL=ON' + CMAKE_GENERATE: '-A x64 -DCURL_USE_SCHANNEL=OFF -DCURL_USE_OPENSSL=ON' - job_name: 'CM VS2026, Release, arm64, Schannel, Static, Build-tests' APPVEYOR_BUILD_WORKER_IMAGE: 'Visual Studio 2026' CMAKE_VERSION: 4.2.1 # manually bumped CMAKE_SHA256: dfc2b2afac257555e3b9ce375b12b2883964283a366c17fec96cf4d17e4f1677 CMAKE_GENERATOR: 'Visual Studio 18 2026' - CMAKE_GENERATE: '-A ARM64 -DENABLE_DEBUG=OFF -DBUILD_SHARED_LIBS=OFF' + CMAKE_GENERATE: '-A ARM64 -DBUILD_SHARED_LIBS=OFF' - job_name: 'CM VS2010, Debug, x64, Schannel, Shared, Build-tests & examples' APPVEYOR_BUILD_WORKER_IMAGE: 'Visual Studio 2015' CMAKE_VERSION: 3.18.4 # manually bumped (lowest minor version supported by curl) CMAKE_SHA256: a932bc0c8ee79f1003204466c525b38a840424d4ae29f9e5fb88959116f2407d CMAKE_GENERATOR: 'Visual Studio 10 2010' - CMAKE_GENERATE: '-A x64' + CMAKE_GENERATE: '-A x64 -DENABLE_DEBUG=ON' - job_name: 'CM VS2012, Release, x86, Schannel, Shared, Build-tests' APPVEYOR_BUILD_WORKER_IMAGE: 'Visual Studio 2015' CMAKE_VERSION: 3.21.7 # manually bumped CMAKE_SHA256: 4c4840e2dc2bf82e8a16081ff506bba54f3a228b91ce36317129fed4035ef2e3 CMAKE_GENERATOR: 'Visual Studio 11 2012' - CMAKE_GENERATE: '-A Win32 -DENABLE_DEBUG=OFF' + CMAKE_GENERATE: '-A Win32' - job_name: 'CM VS2013, Debug, x64, Schannel, Shared' APPVEYOR_BUILD_WORKER_IMAGE: 'Visual Studio 2015' CMAKE_VERSION: 3.18.4 # manually bumped (lowest minor version supported by curl) CMAKE_SHA256: a932bc0c8ee79f1003204466c525b38a840424d4ae29f9e5fb88959116f2407d CMAKE_GENERATOR: 'Visual Studio 12 2013' - CMAKE_GENERATE: '-A x64' + CMAKE_GENERATE: '-A x64 -DENABLE_DEBUG=ON' - job_name: 'CM VS2015, Debug, x64, Schannel, Static' APPVEYOR_BUILD_WORKER_IMAGE: 'Visual Studio 2015' CMAKE_VERSION: 3.19.8 # manually bumped CMAKE_SHA256: 2a30877a3d6b50da305b289f4d1c03befdfaeb2edba02a563c681e883d810380 CMAKE_GENERATOR: 'Visual Studio 14 2015' - CMAKE_GENERATE: '-A x64 -DBUILD_SHARED_LIBS=OFF' + CMAKE_GENERATE: '-A x64 -DENABLE_DEBUG=ON -DBUILD_SHARED_LIBS=OFF' - job_name: 'CM VS2017, Debug, x64, Schannel, Shared' APPVEYOR_BUILD_WORKER_IMAGE: 'Visual Studio 2017' CMAKE_VERSION: 3.20.6 # manually bumped CMAKE_SHA256: f240a38c964712aac474644b3ba21bdc2b4e8d5e31179f67bd2e6f45fa349419 CMAKE_GENERATOR: 'Visual Studio 15 2017' - CMAKE_GENERATE: '-A x64' + CMAKE_GENERATE: '-A x64 -DENABLE_DEBUG=ON' - job_name: 'CM VS2019, Debug, x64, OpenSSL 3.0 + Schannel, Shared, !verbose, Build-tests' APPVEYOR_BUILD_WORKER_IMAGE: 'Visual Studio 2019' CMAKE_GENERATOR: 'Visual Studio 16 2019' - CMAKE_GENERATE: '-A x64 -DCURL_USE_OPENSSL=ON -DCURL_DISABLE_VERBOSE_STRINGS=ON' + CMAKE_GENERATE: '-A x64 -DENABLE_DEBUG=ON -DCURL_USE_OPENSSL=ON -DCURL_DISABLE_VERBOSE_STRINGS=ON' - job_name: 'CM VS2026, Debug, x64, OpenSSL 3.6 + Schannel, Static, Unicode, Build-tests & examples, clang-cl' APPVEYOR_BUILD_WORKER_IMAGE: 'Visual Studio 2026' CMAKE_VERSION: 4.2.1 # manually bumped (first minor version to support VS2026) CMAKE_SHA256: dfc2b2afac257555e3b9ce375b12b2883964283a366c17fec96cf4d17e4f1677 CMAKE_GENERATOR: 'Visual Studio 18 2026' - CMAKE_GENERATE: '-A x64 -T ClangCl -DBUILD_SHARED_LIBS=OFF -DCURL_USE_OPENSSL=ON -DENABLE_UNICODE=ON' + CMAKE_GENERATE: '-A x64 -T ClangCl -DENABLE_DEBUG=ON -DBUILD_SHARED_LIBS=OFF -DCURL_USE_OPENSSL=ON -DENABLE_UNICODE=ON' CREATE_ARTIFACT: 'true' - job_name: 'CM VS2022, Release, x64, Schannel, Shared, Unicode, Build-tests' APPVEYOR_BUILD_WORKER_IMAGE: 'Visual Studio 2022' CMAKE_GENERATOR: 'Visual Studio 17 2022' - CMAKE_GENERATE: '-A x64 -DENABLE_UNICODE=ON -DENABLE_DEBUG=OFF' + CMAKE_GENERATE: '-A x64 -DENABLE_UNICODE=ON' # VisualStudioSolution builds From 413df33799a6a88e5210cb3117d4afd80db3261a Mon Sep 17 00:00:00 2001 From: Viktor Szakats Date: Wed, 7 Oct 2026 09:11:54 +0200 Subject: [PATCH 422/481] GHA/non-native: fail early if `mport` did not complete the install step MidnightBSD's `mport install` is prone to return a non-zero exit code on success (hence the need to use `|| true`; also unrelated to the `grep` filtering, needed to avoid log noise for no built-in option for this). Work around by verifying success explicitly, so that a truly failed install command fails in the 'install prereqs' step, instead of raising a confusing error (e.g. "cmake: not found") in the step after. Reported-by: Dan Fandrich Fixes #23291 Follow-up to b158d1c9f7456a8f976c74c08d2dc5a555e9cc77 #21681 Follow-up to bf29c3e17544ff13c67e94157e6440662317355b #22145 Closes #23294 --- .github/workflows/non-native.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/non-native.yml b/.github/workflows/non-native.yml index c79d7dfbe378..ad0b6c7b555c 100644 --- a/.github/workflows/non-native.yml +++ b/.github/workflows/non-native.yml @@ -145,6 +145,7 @@ jobs: sudo mport upgrade | grep -v -E '(Downloading.+%|^/usr/local)' fi sudo mport install pkgconf brotli libnghttp2 ${MATRIX_INSTALL} | grep -v -E '(Downloading.+%|^/usr/local)' || true + command -v pkg-config >/dev/null 2>&1 elif [ "${MATRIX_OS}" = 'netbsd' ]; then sudo pkgin -y install pkg-config perl brotli libssh2 libpsl nghttp2 ${MATRIX_INSTALL} elif [ "${MATRIX_OS}" = 'openbsd' ]; then From 09f9fed798956f38597197eac6d1f21ac7d9a146 Mon Sep 17 00:00:00 2001 From: Daniel Stenberg Date: Wed, 7 Oct 2026 15:51:28 +0200 Subject: [PATCH 423/481] cookie: accept and ignore empty property names in Set-Cookie Verify with test 3611 Reported-by: Hasan Flayyih Abdullah Closes #23302 --- lib/cookie.c | 3 ++- tests/data/Makefile.am | 1 + tests/data/test3611 | 52 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 55 insertions(+), 1 deletion(-) create mode 100644 tests/data/test3611 diff --git a/lib/cookie.c b/lib/cookie.c index 4aedd728e180..cea93d6142ac 100644 --- a/lib/cookie.c +++ b/lib/cookie.c @@ -587,7 +587,8 @@ parse_cookie_header(struct Curl_easy *data, struct Curl_str name; /* we have a = pair or a stand-alone word here */ - if(!curlx_str_cspn(&ptr, &name, ";\r\n=")) { + int rc = curlx_str_cspn(&ptr, &name, ";\r\n="); + if(!rc || (rc == STRE_SHORT)) { struct Curl_str val; bool sep = FALSE; curlx_str_trimblanks(&name); diff --git a/tests/data/Makefile.am b/tests/data/Makefile.am index 388de64dcc6f..7bfd49766ba4 100644 --- a/tests/data/Makefile.am +++ b/tests/data/Makefile.am @@ -2134,6 +2134,7 @@ TESTCASES = \ test3555 \ test3595 \ test3596 \ + test3611 \ test4000 \ test4001 \ test4039 \ diff --git a/tests/data/test3611 b/tests/data/test3611 new file mode 100644 index 000000000000..c87dda98492a --- /dev/null +++ b/tests/data/test3611 @@ -0,0 +1,52 @@ + + + + +cookies + + + + + +HTTP/1.1 200 OK +Content-Length: 0 +Set-Cookie: session=content;=path=/insane; HttpOnly; Secure; path=/moo + + + + + +# Client-side + + +cookies + + +https + + +Get cookies with a nameless property + + +--resolve my-machine:%HTTPSPORT:%HOSTIP -c "%LOGDIR/dump%TESTNUMBER" https://my-machine:%HTTPSPORT/moo/%TESTNUMBER -k + + + +# Verify data after the test has been "shot" + + +GET /moo/%TESTNUMBER HTTP/1.1 +Host: my-machine:%HTTPSPORT +User-Agent: curl/%VERSION +Accept: */* + + + +# Netscape HTTP Cookie File +# https://curl.se/docs/http-cookies.html +# This file was generated by libcurl! Edit at your own risk. + +#HttpOnly_my-machine%TABFALSE%TAB/moo%TABTRUE%TAB0%TABsession%TABcontent + + + From d8363b77204396bd243132dc07e4536f0daf14ae Mon Sep 17 00:00:00 2001 From: Stefan Eissing Date: Wed, 7 Oct 2026 12:24:19 +0200 Subject: [PATCH 424/481] tool: handling of "synthetic" errors Certain config failures set the flag `synthetic_error` which in most cases fail the easy creation (AFAICT, why exactly this flag is needed is a bit dubious). Make sure that `config2setopts()` always fails with an error when this flag is set. The write callback also did set `synthetic_error` which can confuse subsequent transfer as this may happen at any time when a response is coming in. Do not set the flag in the write callback. Closes #23300 --- src/config2setopts.c | 6 +++++- src/tool_cb_wrt.c | 1 - 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/config2setopts.c b/src/config2setopts.c index a920a7e4aa33..477f15ac7e77 100644 --- a/src/config2setopts.c +++ b/src/config2setopts.c @@ -1237,7 +1237,7 @@ CURLcode config2setopts(struct OperationConfig *config, gen_cb_setopts(config, per, curl); result = proxy_setopts(config, curl); - if(setopt_bad(result) || config->synthetic_error) + if(setopt_bad(result)) return result; result = credentials_and_headers_setopts(config, curl); @@ -1251,5 +1251,9 @@ CURLcode config2setopts(struct OperationConfig *config, result = mail_and_sasl_setopts(config, curl); if(!setopt_bad(result)) result = misc_setopts(config, curl); + + if(!result && config->synthetic_error) + result = CURLE_FAILED_INIT; + return result; } diff --git a/src/tool_cb_wrt.c b/src/tool_cb_wrt.c index 15e5dceb24ce..2a17df0745d3 100644 --- a/src/tool_cb_wrt.c +++ b/src/tool_cb_wrt.c @@ -355,7 +355,6 @@ size_t tool_write_cb(char *buffer, size_t sz, size_t nmemb, void *userdata) warnf("Binary output can mess up your terminal. " "Use \"--output -\" to tell curl to output it to your terminal " "anyway, or consider \"--output \" to save to a file."); - config->synthetic_error = TRUE; return CURL_WRITEFUNC_ERROR; } } From 64fb3e7a7b116f690966054f281624152d4a7651 Mon Sep 17 00:00:00 2001 From: Stefan Eissing Date: Wed, 7 Oct 2026 11:22:39 +0200 Subject: [PATCH 425/481] gnutls: hardening Check the result code of `gnutls_record_send()` for sanity also in production builds. Closes #23297 --- lib/vtls/gtls.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/vtls/gtls.c b/lib/vtls/gtls.c index 06bb2b08f410..25f49b81d0c7 100644 --- a/lib/vtls/gtls.c +++ b/lib/vtls/gtls.c @@ -2095,8 +2095,13 @@ static CURLcode gtls_send(struct Curl_cfilter *cf, nwritten = gnutls_record_send(backend->gtls.session, buf, remain); if(nwritten >= 0) { + if((size_t)nwritten > remain) { + DEBUGASSERT(0); + failf(data, "gnutls_record_send(len=%zu) returned %zd", + remain, nwritten); + return CURLE_SEND_ERROR; + } *pnwritten += (size_t)nwritten; - DEBUGASSERT((size_t)nwritten <= remain); buf = (char *)CURL_UNCONST(buf) + (size_t)nwritten; remain -= (size_t)nwritten; } From 55652ad0def8fa2b46924b1edd3fdf413b7880ad Mon Sep 17 00:00:00 2001 From: Stefan Eissing Date: Wed, 7 Oct 2026 11:57:04 +0200 Subject: [PATCH 426/481] openssl: hardening Check the result of `SSL_write()` for sanity. Closes #23299 --- lib/vtls/openssl.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/vtls/openssl.c b/lib/vtls/openssl.c index 3183d1097057..ace44ca954a7 100644 --- a/lib/vtls/openssl.c +++ b/lib/vtls/openssl.c @@ -5107,8 +5107,14 @@ static CURLcode ossl_send(struct Curl_cfilter *cf, octx->blocked_ssl_write_len = 0; nwritten = SSL_write(octx->ssl, mem, memlen); - if(nwritten > 0) + if(nwritten > 0) { + if(nwritten > memlen) { + failf(data, "SSL_write(len=%d) returned %d", memlen, nwritten); + result = CURLE_SEND_ERROR; + goto out; + } *pnwritten = (size_t)nwritten; + } else { err = SSL_get_error(octx->ssl, nwritten); From 954065b77c80bebd8332548d745a38dbf16f62f6 Mon Sep 17 00:00:00 2001 From: Renan Pimentel Date: Sun, 4 Oct 2026 23:07:49 -0300 Subject: [PATCH 427/481] unit1399: check microsecond precision across redirects Compare timer totals in microseconds instead of truncating to seconds. Use fractional request durations to detect lost sub-second timing while preserving the repeated STARTTRANSFER checks. Closes #23258 --- tests/unit/unit1399.c | 53 ++++++++++++++++++++++--------------------- 1 file changed, 27 insertions(+), 26 deletions(-) diff --git a/tests/unit/unit1399.c b/tests/unit/unit1399.c index 5940975d88c0..159ce17b8cca 100644 --- a/tests/unit/unit1399.c +++ b/tests/unit/unit1399.c @@ -44,36 +44,35 @@ static void t1399_stop(struct Curl_easy *easy) curl_global_cleanup(); } -static bool usec_matches_seconds(timediff_t time_usec, int expected_seconds) +static bool usec_matches(timediff_t time_usec, timediff_t expected_usec) { - static int usec_magnitude = 1000000; - - int time_sec = (int)(time_usec / usec_magnitude); - bool same = (time_sec == expected_seconds); - curl_mfprintf(stderr, "is %d us same as %d seconds? %s\n", - (int)time_usec, expected_seconds, - same ? "Yes" : "No"); + bool same = (time_usec == expected_usec); + curl_mfprintf(stderr, "is %" FMT_TIMEDIFF_T + " us same as %" FMT_TIMEDIFF_T " us? %s\n", + time_usec, expected_usec, same ? "Yes" : "No"); return same; } -static void expect_timer_seconds(struct Curl_easy *data, int seconds) +static void expect_timer_microseconds(struct Curl_easy *data, + timediff_t microseconds) { struct Progress *p = &data->progress; char msg[64]; - curl_msnprintf(msg, sizeof(msg), "about %d seconds should have passed", - seconds); - fail_unless(usec_matches_seconds(p->total.nslookup_us, seconds), msg); - fail_unless(usec_matches_seconds(p->total.connect_us, seconds), msg); - fail_unless(usec_matches_seconds(p->total.appconnect_us, seconds), msg); - fail_unless(usec_matches_seconds(p->total.pretransfer_us, seconds), msg); - fail_unless(usec_matches_seconds(p->total.starttransfer_us, seconds), msg); + curl_msnprintf(msg, sizeof(msg), + "%" FMT_TIMEDIFF_T " microseconds should have passed", + microseconds); + fail_unless(usec_matches(p->total.nslookup_us, microseconds), msg); + fail_unless(usec_matches(p->total.connect_us, microseconds), msg); + fail_unless(usec_matches(p->total.appconnect_us, microseconds), msg); + fail_unless(usec_matches(p->total.pretransfer_us, microseconds), msg); + fail_unless(usec_matches(p->total.starttransfer_us, microseconds), msg); } /* Scenario: simulate a redirect. When a redirect occurs, t_nslookup, * t_connect, t_appconnect, t_pretransfer, and t_starttransfer are additive. - * E.g., if t_starttransfer took 2 seconds initially and took another 1 - * second for the redirect request, then the resulting t_starttransfer should - * be 3 seconds. */ + * E.g., if t_starttransfer took 2.25 seconds initially and took another 1.5 + * seconds for the redirect request, then the resulting t_starttransfer should + * be 3.75 seconds. */ static CURLcode test_unit1399(const char *arg) { struct Curl_easy *data; @@ -89,15 +88,16 @@ static CURLcode test_unit1399(const char *arg) /* Record the first request start */ Curl_pgrsTimeWas(data, TIMER_STARTSINGLE, now); - /* Let 2 seconds pass */ + /* Let 2.25 seconds pass */ now.tv_sec += 2; + now.tv_usec = 250000; Curl_pgrsTimeWas(data, TIMER_NAMELOOKUP, now); Curl_pgrsTimeWas(data, TIMER_CONNECT, now); Curl_pgrsTimeWas(data, TIMER_APPCONNECT, now); Curl_pgrsTimeWas(data, TIMER_PRETRANSFER, now); Curl_pgrsTimeWas(data, TIMER_STARTTRANSFER, now); - expect_timer_seconds(data, 2); + expect_timer_microseconds(data, 2250000); /* now simulate the redirect after one second * and subsequent follow request start */ @@ -105,8 +105,9 @@ static CURLcode test_unit1399(const char *arg) Curl_pgrsTimeWas(data, TIMER_REDIRECT, now); Curl_pgrsTimeWas(data, TIMER_STARTSINGLE, now); - /* Let 1 second pass */ + /* Let 1.5 seconds pass */ now.tv_sec += 1; + now.tv_usec = 750000; Curl_pgrsTimeWas(data, TIMER_NAMELOOKUP, now); Curl_pgrsTimeWas(data, TIMER_CONNECT, now); Curl_pgrsTimeWas(data, TIMER_APPCONNECT, now); @@ -118,10 +119,10 @@ static CURLcode test_unit1399(const char *arg) Curl_pgrsTimeWas(data, TIMER_STARTTRANSFER, now); Curl_pgrsTimeWas(data, TIMER_STARTTRANSFER, now); - /* Accumulated times are now 3 seconds: - * - 2 after 1st request start - * - 1 after 2nd requerst start */ - expect_timer_seconds(data, 3); + /* Accumulated times are now 3.75 seconds: + * - 2.25 after 1st request start + * - 1.5 after 2nd request start */ + expect_timer_microseconds(data, 3750000); UNITTEST_END(t1399_stop(data)) } From 20e7d0a2aadf235a65e6c18fc1d99b36cf9e65aa Mon Sep 17 00:00:00 2001 From: Jorge Rocamora <33847633+aeroyorch@users.noreply.github.com> Date: Sun, 4 Oct 2026 20:12:41 +0200 Subject: [PATCH 428/481] tests: add SFTP connection reuse tests Closes #23254 --- tests/http/test_51_sftp.py | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/tests/http/test_51_sftp.py b/tests/http/test_51_sftp.py index 4150e3972ca0..878b2e434531 100644 --- a/tests/http/test_51_sftp.py +++ b/tests/http/test_51_sftp.py @@ -137,6 +137,40 @@ def test_51_12_dl_parallel(self, env: Env, sshd: Sshd): r.check_exit_code(0) self.check_downloads(curl, doc_file, count) + def test_51_13_dl_reuse(self, env: Env, sshd: Sshd): + count = 3 + curl = CurlClient(env=env) + doc_file = os.path.join(sshd.home_dir, 'data-10k') + url = f'sftp://{env.domain1}:{sshd.port}/{doc_file}?[0-{count-1}]' + r = curl.ssh_download(urls=[url], extra_args=[ + '--knownhosts', sshd.known_hosts, + '--pubkey', sshd.user1_pubkey_file, + '--key', sshd.user1_privkey_file, + '--user', f'{os.environ["USER"]}:', + ]) + r.check_exit_code(0) + assert len(r.stats) == count, r.dump_logs() + assert r.total_connects == 1, r.dump_logs() + + def test_51_14_dl_no_reuse(self, env: Env, sshd: Sshd): + if not env.curl_is_debug(): + pytest.skip('only works for curl debug builds') + count = 3 + run_env = os.environ.copy() + run_env['CURL_FORBID_REUSE'] = '1' + curl = CurlClient(env=env, run_env=run_env) + doc_file = os.path.join(sshd.home_dir, 'data-10k') + url = f'sftp://{env.domain1}:{sshd.port}/{doc_file}?[0-{count-1}]' + r = curl.ssh_download(urls=[url], extra_args=[ + '--knownhosts', sshd.known_hosts, + '--pubkey', sshd.user1_pubkey_file, + '--key', sshd.user1_privkey_file, + '--user', f'{os.environ["USER"]}:', + ]) + r.check_exit_code(0) + assert len(r.stats) == count, r.dump_logs() + assert r.total_connects == count, r.dump_logs() + def test_51_20_ul_single(self, env: Env, sshd: Sshd): srcfile = os.path.join(env.gen_dir, 'data-10k') destfile = os.path.join(sshd.home_dir, 'upload_20.data') From 10bf0362826681dd745b8eb06dee2af23a6a317c Mon Sep 17 00:00:00 2001 From: dataisland Date: Sun, 6 Sep 2026 19:44:55 -0700 Subject: [PATCH 429/481] share link: forget connection When adding a share to an easy handle and the share caches connections, invalidate the easy handle's last_conn_id and last_cpid. Future lookups use the new connection cache and could otherwise find a different connection with the same pool-local identifier. Add a regression test for the private-to-shared transition. Follow-up to fe2d3591c Bug: https://hackerone.com/reports/3971585 Closes #22853 --- lib/curl_share.c | 6 ++ tests/data/Makefile.am | 1 + tests/data/test3234 | 35 ++++++++++++ tests/libtest/Makefile.inc | 1 + tests/libtest/lib3234.c | 110 +++++++++++++++++++++++++++++++++++++ 5 files changed, 153 insertions(+) create mode 100644 tests/data/test3234 create mode 100644 tests/libtest/lib3234.c diff --git a/lib/curl_share.c b/lib/curl_share.c index f457ab6e280d..21ed3e9ec679 100644 --- a/lib/curl_share.c +++ b/lib/curl_share.c @@ -458,6 +458,12 @@ CURLcode Curl_share_easy_link(struct Curl_easy *data, if(share) { bool locked = share_lock_acquire(share, data); + /* Forget identifiers from the previously used connection cache. */ + if((share->specifier & (1 << CURL_LOCK_DATA_CONNECT))) { + data->state.last_conn_id = -1; + data->state.last_cpid = UINT32_MAX; + } + share_ref_inc(share); data->share = share; diff --git a/tests/data/Makefile.am b/tests/data/Makefile.am index 7bfd49766ba4..095862540631 100644 --- a/tests/data/Makefile.am +++ b/tests/data/Makefile.am @@ -2119,6 +2119,7 @@ TESTCASES = \ test3231 \ test3232 \ test3233 \ + test3234 \ test3271 \ test3300 \ test3301 \ diff --git a/tests/data/test3234 b/tests/data/test3234 new file mode 100644 index 000000000000..2775b3c26219 --- /dev/null +++ b/tests/data/test3234 @@ -0,0 +1,35 @@ + + + + +HTTP +CONNECT_ONLY +CURLOPT_SHARE +libtest + + + +# Server-side + + + +# Client-side + + +http + + +http + + +CONNECT_ONLY forgets connection when attaching a shared pool + + +http://%HOSTIP:%HTTPPORT/%TESTNUMBER + + + +# Verify data after the test has been "shot" + + + diff --git a/tests/libtest/Makefile.inc b/tests/libtest/Makefile.inc index 99e518567cfe..0395dbb3a296 100644 --- a/tests/libtest/Makefile.inc +++ b/tests/libtest/Makefile.inc @@ -342,6 +342,7 @@ TESTS_C = \ lib3208.c \ lib3231.c \ lib3233.c \ + lib3234.c \ lib3595.c \ lib3596.c \ lib5000.c \ diff --git a/tests/libtest/lib3234.c b/tests/libtest/lib3234.c new file mode 100644 index 000000000000..2d9fb9f8367c --- /dev/null +++ b/tests/libtest/lib3234.c @@ -0,0 +1,110 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ +#include "first.h" + +static CURLcode test_lib3234(const char *URL) +{ + CURL *easy_a = NULL; + CURL *easy_b = NULL; + CURLSH *share = NULL; + CURLSHcode shresult; + CURLcode result = CURLE_OK; + curl_socket_t socket_a = CURL_SOCKET_BAD; + curl_socket_t socket_b = CURL_SOCKET_BAD; + curl_socket_t socket_after = CURL_SOCKET_BAD; + curl_off_t conn_id = -1; + size_t sent = 0; + + global_init(CURL_GLOBAL_ALL); + + share = curl_share_init(); + if(!share) { + result = TEST_ERR_FAILURE; + goto test_cleanup; + } + + shresult = curl_share_setopt(share, CURLSHOPT_SHARE, + CURL_LOCK_DATA_CONNECT); + if(shresult != CURLSHE_OK) { + result = TEST_ERR_FAILURE; + goto test_cleanup; + } + + easy_init(easy_a); + easy_setopt(easy_a, CURLOPT_URL, URL); + easy_setopt(easy_a, CURLOPT_CONNECT_ONLY, 1L); + result = curl_easy_perform(easy_a); + if(result) + goto test_cleanup; + result = curl_easy_getinfo(easy_a, CURLINFO_ACTIVESOCKET, &socket_a); + if(result) + goto test_cleanup; + + easy_init(easy_b); + easy_setopt(easy_b, CURLOPT_SHARE, share); + easy_setopt(easy_b, CURLOPT_URL, URL); + easy_setopt(easy_b, CURLOPT_CONNECT_ONLY, 1L); + result = curl_easy_perform(easy_b); + if(result) + goto test_cleanup; + result = curl_easy_getinfo(easy_b, CURLINFO_ACTIVESOCKET, &socket_b); + if(result) + goto test_cleanup; + + if(socket_a == CURL_SOCKET_BAD || socket_b == CURL_SOCKET_BAD || + socket_a == socket_b) { + curl_mfprintf(stderr, "failed to create two distinct connections\n"); + result = TEST_ERR_FAILURE; + goto test_cleanup; + } + + easy_setopt(easy_a, CURLOPT_SHARE, share); + result = curl_easy_getinfo(easy_a, CURLINFO_CONN_ID, &conn_id); + if(result) + goto test_cleanup; + result = curl_easy_getinfo(easy_a, CURLINFO_ACTIVESOCKET, &socket_after); + if(result) + goto test_cleanup; + + if(conn_id != -1 || socket_after != CURL_SOCKET_BAD) { + curl_mfprintf(stderr, "connection survived a connection pool change\n"); + result = TEST_ERR_FAILURE; + goto test_cleanup; + } + + result = curl_easy_send(easy_a, "x", 1, &sent); + if(result != CURLE_UNSUPPORTED_PROTOCOL || sent) { + curl_mfprintf(stderr, "raw send used a connection from the new pool\n"); + result = TEST_ERR_FAILURE; + goto test_cleanup; + } + result = CURLE_OK; + +test_cleanup: + curl_easy_cleanup(easy_a); + curl_easy_cleanup(easy_b); + curl_share_cleanup(share); + curl_global_cleanup(); + return result; +} From e634352f267c3bcb9023bb7c122762f2fc38b09c Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Fri, 2 Oct 2026 19:00:18 +0200 Subject: [PATCH 430/481] cf-https-connect: re-arm the timer for the hard eyeballs timeout When the first attempt has seen data at the soft timeout, nothing armed a timer for the hard timeout, so the second attempt started on the next unrelated wakeup only. With HTTP/3 to a host that answers on UDP but does not complete the handshake, that is the QUIC retransmission timer, about one second later, whatever the happy eyeballs timeout is. Follow-up to 567803db797ae656dbdf4a630785e51b68798060 #21267 Closes #23238 --- lib/cf-https-connect.c | 18 ++++++---- lib/vquic/vquic.c | 28 ++++++++++----- tests/http/test_06_eyeballs.py | 65 ++++++++++++++++++++++++++++++++++ 3 files changed, 95 insertions(+), 16 deletions(-) diff --git a/lib/cf-https-connect.c b/lib/cf-https-connect.c index 5c8a7a92029c..56993a9d1a81 100644 --- a/lib/cf-https-connect.c +++ b/lib/cf-https-connect.c @@ -259,13 +259,17 @@ static bool time_to_start_baller2(struct Curl_cfilter *cf, ctx->hard_eyeballs_timeout_ms, ctx->ballers[1].name); return TRUE; } - else if(elapsed_ms >= ctx->soft_eyeballs_timeout_ms && - cf_hc_baller_reply_ms(&ctx->ballers[0], data) < 0) { - CURL_TRC_CF(data, cf, "%s has not seen any data after %" - FMT_TIMEDIFF_T "ms, starting %s", - ctx->ballers[0].name, ctx->soft_eyeballs_timeout_ms, - ctx->ballers[1].name); - return TRUE; + else if(elapsed_ms >= ctx->soft_eyeballs_timeout_ms) { + if(cf_hc_baller_reply_ms(&ctx->ballers[0], data) < 0) { + CURL_TRC_CF(data, cf, "%s has not seen any data after %" + FMT_TIMEDIFF_T "ms, starting %s", + ctx->ballers[0].name, ctx->soft_eyeballs_timeout_ms, + ctx->ballers[1].name); + return TRUE; + } + /* set the effective hard timeout again */ + Curl_expire_set(data, EXPIRE_ALPN_EYEBALLS, + ctx->hard_eyeballs_timeout_ms - elapsed_ms, pnow); } return FALSE; } diff --git a/lib/vquic/vquic.c b/lib/vquic/vquic.c index a47a78e9f287..cdf97ac5bf74 100644 --- a/lib/vquic/vquic.c +++ b/lib/vquic/vquic.c @@ -631,7 +631,8 @@ static CURLcode recvmmsg_packets(struct Curl_cfilter *cf, struct Curl_easy *data, struct cf_quic_ctx *qctx, size_t max_pkts, - Curl_vquic_recv_pkts_cb *recv_cb, void *userp) + Curl_vquic_recv_pkts_cb *recv_cb, void *userp, + size_t *pnread) { #if defined(__linux__) && defined(UDP_GRO) #define MMSG_NUM 16 @@ -733,6 +734,7 @@ static CURLcode recvmmsg_packets(struct Curl_cfilter *cf, "vquic_recvmmsg(len=%zu, packets=%zu, calls=%zu) -> %d", total_nread, pkts, calls, (int)result); Curl_multi_xfer_sockbuf_release(data, sockbuf); + *pnread = total_nread; return result; } @@ -743,7 +745,7 @@ static CURLcode recvmsg_x_packets(struct Curl_cfilter *cf, struct cf_quic_ctx *qctx, size_t max_pkts, Curl_vquic_recv_pkts_cb *recv_cb, - void *userp) + void *userp, size_t *pnread) { #define MSG_X_NUM 64 #define MSG_BUF_SIZE (2048) @@ -843,6 +845,7 @@ static CURLcode recvmsg_x_packets(struct Curl_cfilter *cf, "vquic_recvmsg_x(len=%zu, packets=%zu, calls=%zu) -> %d", total_nread, pkts, calls, (int)result); Curl_multi_xfer_sockbuf_release(data, sockbuf); + *pnread = total_nread; return result; } @@ -851,7 +854,8 @@ static CURLcode recvmsg_packets(struct Curl_cfilter *cf, struct Curl_easy *data, struct cf_quic_ctx *qctx, size_t max_pkts, - Curl_vquic_recv_pkts_cb *recv_cb, void *userp) + Curl_vquic_recv_pkts_cb *recv_cb, void *userp, + size_t *pnread) { #define CMSG_PER_MSG_SIZE CMSG_SPACE(sizeof(int)) struct iovec msg_iov; @@ -929,6 +933,7 @@ static CURLcode recvmsg_packets(struct Curl_cfilter *cf, CURL_TRC_CF(data, cf, "vquic_recvmsg(len=%zu, packets=%zu, calls=%zu) -> %d", total_nread, pkts, calls, (int)result); + *pnread = total_nread; return result; } @@ -937,7 +942,8 @@ static CURLcode recvfrom_packets(struct Curl_cfilter *cf, struct Curl_easy *data, struct cf_quic_ctx *qctx, size_t max_pkts, - Curl_vquic_recv_pkts_cb *recv_cb, void *userp) + Curl_vquic_recv_pkts_cb *recv_cb, void *userp, + size_t *pnread) { uint8_t buf[64 * 1024]; int bufsize = (int)sizeof(buf); @@ -994,6 +1000,7 @@ static CURLcode recvfrom_packets(struct Curl_cfilter *cf, CURL_TRC_CF(data, cf, "vquic_recvfrom(len=%zu, packets=%zu, calls=%zu) -> %d", total_nread, pkts, calls, (int)result); + *pnread = total_nread; return result; } #endif /* !HAVE_SENDMMSG && !HAVE_SENDMSG */ @@ -1005,17 +1012,20 @@ CURLcode Curl_vquic_recv_packets(struct Curl_cfilter *cf, Curl_vquic_recv_pkts_cb *recv_cb, void *userp) { CURLcode result; + size_t nread = 0; #ifdef HAVE_SENDMMSG - result = recvmmsg_packets(cf, data, qctx, max_pkts, recv_cb, userp); + result = recvmmsg_packets(cf, data, qctx, max_pkts, recv_cb, userp, &nread); #elif defined(HAVE_APPLE_MSG_X) - result = recvmsg_x_packets(cf, data, qctx, max_pkts, recv_cb, userp); + result = recvmsg_x_packets(cf, data, qctx, max_pkts, recv_cb, userp, + &nread); #elif defined(HAVE_SENDMSG) - result = recvmsg_packets(cf, data, qctx, max_pkts, recv_cb, userp); + result = recvmsg_packets(cf, data, qctx, max_pkts, recv_cb, userp, &nread); #else - result = recvfrom_packets(cf, data, qctx, max_pkts, recv_cb, userp); + result = recvfrom_packets(cf, data, qctx, max_pkts, recv_cb, userp, &nread); #endif if(!result) { - if(!qctx->got_first_byte) { + /* EAGAIN is no error, only count actual packets as a reply */ + if(!qctx->got_first_byte && nread) { qctx->got_first_byte = TRUE; qctx->first_byte_at = qctx->last_op; } diff --git a/tests/http/test_06_eyeballs.py b/tests/http/test_06_eyeballs.py index afead121139c..fb5f57493931 100644 --- a/tests/http/test_06_eyeballs.py +++ b/tests/http/test_06_eyeballs.py @@ -24,6 +24,8 @@ # import logging import re +import socket +import threading import pytest from testenv import CurlClient, Env @@ -68,6 +70,69 @@ def test_06_04_h3_fallback_h1(self, env: Env, httpd, nghttpx): r.check_response(count=1, http_status=200) assert r.stats[0]['http_version'] == '1.1' + # download using HTTP/3 on a server that silently drops UDP: fallback on + # h2 at the soft timeout, since QUIC never sees any data + @pytest.mark.skipif(condition=not Env.have_h3(), reason="missing HTTP/3 support") + @pytest.mark.skipif(condition=not Env.curl_is_verbose(), reason="needs curl verbose strings") + def test_06_05_h3_silent_fallback_h2(self, env: Env, httpd, nghttpx): + curl = CurlClient(env=env) + urln = f'https://{env.domain1}:{env.https_only_tcp_port}/data.json' + # take the UDP port without reading it, so that packets are not refused + with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as sock: + sock.bind(('127.0.0.1', env.https_only_tcp_port)) + r = curl.http_download(urls=[urln], extra_args=[ + '--http3', '--happy-eyeballs-timeout-ms', '200', + '--trace-config', 'https-connect' + ]) + r.check_response(count=1, http_status=200) + assert r.stats[0]['http_version'] == '2' + soft_timeouts = [line for line in r.trace_lines + if re.match(r'.*h3 has not seen any data after 100ms', line)] + assert len(soft_timeouts) == 1, f'{r.dump_logs()}' + + # download using HTTP/3 on a server that answers UDP with junk: QUIC has + # seen data, fallback on h2 at the hard timeout + @pytest.mark.skipif(condition=not Env.have_h3(), reason="missing HTTP/3 support") + @pytest.mark.skipif(condition=not Env.curl_uses_lib('ngtcp2'), reason="requires ngtcp2 packet handling") + @pytest.mark.skipif(condition=not Env.curl_is_verbose(), reason="needs curl verbose strings") + def test_06_06_h3_stalled_fallback_h2(self, env: Env, httpd, nghttpx): + curl = CurlClient(env=env) + urln = f'https://{env.domain1}:{env.https_only_tcp_port}/data.json' + with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as sock: + sock.bind(('127.0.0.1', env.https_only_tcp_port)) + sock.settimeout(0.1) + done = threading.Event() + + def answer_junk(): + while not done.is_set(): + try: + _, addr = sock.recvfrom(65536) + sock.sendto(b'\0', addr) + except socket.timeout: + pass + + t = threading.Thread(target=answer_junk) + t.start() + try: + r = curl.http_download(urls=[urln], extra_args=[ + '--http3', '--happy-eyeballs-timeout-ms', '200', + '--trace-config', 'https-connect,timer' + ]) + finally: + done.set() + t.join() + r.check_response(count=1, http_status=200) + assert r.stats[0]['http_version'] == '2' + hard_timeouts = [line for line in r.trace_lines + if re.match(r'.*h3 inconclusive after 200', line)] + assert len(hard_timeouts) == 1, f'{r.dump_logs()}' + # the timer is set again for the hard timeout, QUIC does not wake us + # up before its retransmission after about one second + he_timers_set = [line for line in r.trace_lines + if re.match(r'.*\[TIMER] \[ALPN_EYEBALLS] set for', line)] + assert len(he_timers_set) >= 2, f'{r.dump_logs()}' + assert r.stats[0]['time_connect'] < 0.8, f'{r.stats[0]}' + # make a successful https: transfer and observer the timer stats def test_06_10_stats_success(self, env: Env, httpd, nghttpx): curl = CurlClient(env=env) From b32baabf922adc8e81183872bb904aa3b663c324 Mon Sep 17 00:00:00 2001 From: Kuroda Kayn Date: Sat, 3 Oct 2026 17:34:11 +0800 Subject: [PATCH 431/481] pingpong: schedule server response timeouts for the socket API The nonblocking pingpong state machine checks the response deadline without scheduling a timer. A silent server can therefore leave curl_multi_socket_action() waiting indefinitely. Register the remaining time with a dedicated expiration ID so the event loop wakes up to check the response deadline. Add regression tests for FTP using the existing event-driven client with an independent test deadline. Fixes #23025 Closes #23246 --- lib/curl_trc.c | 1 + lib/pingpong.c | 5 +++- lib/urldata.h | 1 + tests/data/Makefile.am | 1 + tests/data/test2330 | 45 +++++++++++++++++++++++++++++++++ tests/libtest/cli_ev_download.c | 28 ++++++++++++++++++-- tests/runner.pm | 2 +- 7 files changed, 79 insertions(+), 4 deletions(-) create mode 100644 tests/data/test2330 diff --git a/lib/curl_trc.c b/lib/curl_trc.c index 6d9b2f31aa87..62bac14fe6d7 100644 --- a/lib/curl_trc.c +++ b/lib/curl_trc.c @@ -316,6 +316,7 @@ static const char * const Curl_trc_timer_names[] = { "FTP_ACCEPT", "ALPN_EYEBALLS", "SHUTDOWN", + "SERVER_RESPONSE", }; static const char *trc_timer_name(int tid) diff --git a/lib/pingpong.c b/lib/pingpong.c index cf958b748671..6a683c12f796 100644 --- a/lib/pingpong.c +++ b/lib/pingpong.c @@ -80,8 +80,11 @@ CURLcode Curl_pp_statemach(struct Curl_easy *data, if(timeout_ms < interval_ms) interval_ms = timeout_ms; } - else + else { interval_ms = 0; /* immediate */ + /* Wake the multi socket API even when the server sends no response. */ + Curl_expire(data, timeout_ms, EXPIRE_SERVER_RESPONSE); + } if(Curl_conn_data_pending(data, FIRSTSOCKET)) rc = 1; diff --git a/lib/urldata.h b/lib/urldata.h index 449a7bac334e..a759de5c7aa5 100644 --- a/lib/urldata.h +++ b/lib/urldata.h @@ -503,6 +503,7 @@ typedef enum { EXPIRE_FTP_ACCEPT, EXPIRE_ALPN_EYEBALLS, EXPIRE_SHUTDOWN, + EXPIRE_SERVER_RESPONSE, EXPIRE_LAST /* not an actual timer, used as a marker only */ } expire_id; diff --git a/tests/data/Makefile.am b/tests/data/Makefile.am index 095862540631..628614dc75e6 100644 --- a/tests/data/Makefile.am +++ b/tests/data/Makefile.am @@ -1966,6 +1966,7 @@ TESTCASES = \ test2310 \ test2311 \ test2318 \ + test2330 \ test2349 \ test2380 \ test2397 \ diff --git a/tests/data/test2330 b/tests/data/test2330 new file mode 100644 index 000000000000..ea79efb3cf9e --- /dev/null +++ b/tests/data/test2330 @@ -0,0 +1,45 @@ + + + + +FTP +timeout +multi_socket +libtest + + + + + +DELAY USER 5 + + + + + +ftp + + +ftp + + +ftp + + +FTP server response timeout with the socket action API + + +cli_ev_download + +# The test's own deadline must not drive libcurl's timeout handling. + +-n 1 -t 1000 -m 3000 ftp://%HOSTIP:%FTPPORT/%TESTNUMBER + + + + + +28 + + + diff --git a/tests/libtest/cli_ev_download.c b/tests/libtest/cli_ev_download.c index e3ca6ad406a7..5aa99bedc1e2 100644 --- a/tests/libtest/cli_ev_download.c +++ b/tests/libtest/cli_ev_download.c @@ -166,6 +166,8 @@ static void usage_ev_download(const char *msg) " event-based downloads with connection reuse forbidden\n" " -n number of transfers to do serially (default: 5)\n" " -C certfile for CA verification\n" + " -t ms server response timeout (default: libcurl default)\n" + " -m ms test time limit per transfer (default: 30000)\n" ); } @@ -176,6 +178,8 @@ static CURLcode test_cli_ev_download(const char *URL) char *cafile = NULL; const char *url; size_t transfer_count = 5; + long response_timeout_ms = 0; + long test_timeout_ms = 30000; size_t i; int watched = 0; struct curltime started; @@ -186,7 +190,7 @@ static CURLcode test_cli_ev_download(const char *URL) memset(&ctx, 0, sizeof(ctx)); ctx.timer_ms = -1; - while((ch = cgetopt(test_argc, test_argv, "hn:C:")) != -1) { + while((ch = cgetopt(test_argc, test_argv, "hn:C:t:m:")) != -1) { switch(ch) { case 'h': usage_ev_download(NULL); @@ -203,6 +207,21 @@ static CURLcode test_cli_ev_download(const char *URL) curlx_free(cafile); cafile = curlx_strdup(coptarg); break; + case 't': + case 'm': { + const char *opt = coptarg; + curl_off_t num; + if(curlx_str_number(&opt, &num, LONG_MAX)) { + usage_ev_download("invalid timeout"); + result = (CURLcode)1; + goto optcleanup; + } + if(ch == 't') + response_timeout_ms = (long)num; + else + test_timeout_ms = (long)num; + break; + } default: usage_ev_download("invalid option"); result = (CURLcode)1; @@ -252,6 +271,9 @@ static CURLcode test_cli_ev_download(const char *URL) curl_easy_setopt(easy, CURLOPT_WRITEFUNCTION, ev_dl_discard_cb); curl_easy_setopt(easy, CURLOPT_FORBID_REUSE, 1L); curl_easy_setopt(easy, CURLOPT_VERBOSE, 1L); + if(response_timeout_ms) + curl_easy_setopt(easy, CURLOPT_SERVER_RESPONSE_TIMEOUT_MS, + response_timeout_ms); if(cafile) curl_easy_setopt(easy, CURLOPT_CAINFO, cafile); @@ -276,9 +298,11 @@ static CURLcode test_cli_ev_download(const char *URL) curl_mfprintf(stderr, "[t-%zu] FINISHED with result %d\n", i, (int)result); } - else if(curlx_timediff_ms(curlx_now(), started) > (timediff_t)30000) { + else if(curlx_timediff_ms(curlx_now(), started) > test_timeout_ms) { curl_mfprintf(stderr, "[t-%zu] transfer timed out\n", i); result = (CURLcode)1; + curl_multi_remove_handle(multi, easy); + curl_easy_cleanup(easy); goto cleanup; } } diff --git a/tests/runner.pm b/tests/runner.pm index f1fa3f6b5efc..dd2dfc44dc43 100644 --- a/tests/runner.pm +++ b/tests/runner.pm @@ -916,7 +916,7 @@ sub singletest_run { $tool = $tool_name . exe_ext('TOOL'); } - if($tool =~ /^lib/) { + if($tool =~ /^(lib|cli_)/) { $tool = "libtests" . exe_ext('TOOL'); $CMDLINE = $LIBDIR . $tool; } From 15ad1b85653afe2fa7ba8249e93581bc86cb52b9 Mon Sep 17 00:00:00 2001 From: Stefan Eissing Date: Mon, 28 Sep 2026 13:57:42 +0200 Subject: [PATCH 432/481] ECH: rework config, add test cases Move the easy configuration for `tsl_ech` into `struct ssl_easy_config`. Add `ech`, `ech_config` and `ech_public` to `struct ssl_filter_config`. Use the ECH config values in connection matching, so that different settings for the same origin to not get mixed up. Add test_23 for checking ECH with nghttpx if - nghttpx supports ECH - the `openssl` command is from OpenSSL 4 or later Verifies all ECH modes good/bad configs and the public name with OpenSSL. Closes #23136 --- lib/setopt.c | 16 +-- lib/urldata.h | 3 - lib/vquic/cf-ngtcp2-cmn.c | 14 +-- lib/vquic/cf-ngtcp2.c | 4 +- lib/vquic/cf-ngtcp2.h | 5 +- lib/vquic/cf-quiche.c | 7 +- lib/vquic/cf-quiche.h | 3 +- lib/vquic/vquic.c | 15 ++- lib/vtls/openssl.c | 87 +++++++--------- lib/vtls/openssl.h | 3 - lib/vtls/rustls.c | 38 +++---- lib/vtls/vtls.c | 29 +++++- lib/vtls/vtls.h | 10 +- lib/vtls/vtls_config.c | 33 ++++++ lib/vtls/vtls_config.h | 10 ++ lib/vtls/wolfssl.c | 41 +++----- lib/vtls/wolfssl.h | 3 - src/config2setopts.c | 4 +- tests/http/Makefile.am | 1 + tests/http/conftest.py | 14 ++- tests/http/test_23_ech.py | 178 +++++++++++++++++++++++++++++++++ tests/http/testenv/__init__.py | 2 +- tests/http/testenv/env.py | 65 +++++++++++- tests/http/testenv/nghttpx.py | 77 ++++++++++++++ 24 files changed, 505 insertions(+), 157 deletions(-) create mode 100644 tests/http/test_23_ech.py diff --git a/lib/setopt.c b/lib/setopt.c index 3cac55678f2e..754bb2240e16 100644 --- a/lib/setopt.c +++ b/lib/setopt.c @@ -1845,26 +1845,26 @@ static CURLcode setopt_ech(struct Curl_easy *data, const char *ptr) CURLcode result = CURLE_OK; if(!ptr || !strcmp(ptr, "false")) - s->tls_ech = CURLECH_DISABLE; + s->ssl.ech = CURLECH_DISABLE; else { size_t plen = strlen(ptr); if(plen > CURL_MAX_INPUT_LENGTH) result = CURLE_BAD_FUNCTION_ARGUMENT; else { if(!strcmp(ptr, "grease")) - s->tls_ech = CURLECH_GREASE; + s->ssl.ech = CURLECH_GREASE; else if(!strcmp(ptr, "true")) - s->tls_ech = CURLECH_ENABLE; + s->ssl.ech = CURLECH_ENABLE; else if(!strcmp(ptr, "hard")) - s->tls_ech = CURLECH_HARD; + s->ssl.ech = CURLECH_HARD; else if(plen > 4 && !strncmp(ptr, "ecl:", 4)) { - if(!s->tls_ech) - s->tls_ech = CURLECH_HARD; + if(!s->ssl.ech) + s->ssl.ech = CURLECH_HARD; result = Curl_setstropt(data, STRING_ECH_CONFIG, ptr + 4); } else if(plen > 3 && !strncmp(ptr, "pn:", 3)) { - if(!s->tls_ech) - s->tls_ech = CURLECH_HARD; + if(!s->ssl.ech) + s->ssl.ech = CURLECH_HARD; result = Curl_setstropt(data, STRING_ECH_PUBLIC, ptr + 3); } else diff --git a/lib/urldata.h b/lib/urldata.h index a759de5c7aa5..6880783b5f35 100644 --- a/lib/urldata.h +++ b/lib/urldata.h @@ -1003,9 +1003,6 @@ struct UserDefined { #ifndef CURL_DISABLE_RTSP uint8_t rtspreq; /* RTSP request type */ #endif -#ifdef USE_ECH - uint8_t tls_ech; /* TLS ECH configuration */ -#endif #ifndef CURL_DISABLE_NETRC uint8_t use_netrc; /* enum CURL_NETRC_OPTION values */ #endif diff --git a/lib/vquic/cf-ngtcp2-cmn.c b/lib/vquic/cf-ngtcp2-cmn.c index 4dc4ffa6e990..f81c67cc1f6a 100644 --- a/lib/vquic/cf-ngtcp2-cmn.c +++ b/lib/vquic/cf-ngtcp2-cmn.c @@ -692,18 +692,6 @@ static ngtcp2_callbacks ng_callbacks = { #pragma warning(pop) #endif -static bool cf_ngtcp2_need_httpsrr(struct Curl_easy *data) -{ -#ifdef USE_OPENSSL - return Curl_ossl_need_httpsrr(data); -#elif defined(USE_WOLFSSL) - return Curl_wssl_need_httpsrr(data); -#else - (void)data; - return FALSE; -#endif -} - #ifdef USE_OPENSSL /* The "new session" callback must return zero if the session can be removed * or non-zero if the session has been put into the session cache. @@ -1146,7 +1134,7 @@ CURLcode Curl_cf_ngtcp2_cmn_connect(struct Curl_cfilter *cf, *done = FALSE; - if(cf_ngtcp2_need_httpsrr(data) && + if(Curl_ssl_need_httpsrr(cf) && !Curl_conn_dns_resolved_https(data, cf->sockindex, ctx->ssl_peer.peer)) { CURL_TRC_CF(data, cf, "need HTTPS-RR, delaying connect"); return CURLE_OK; diff --git a/lib/vquic/cf-ngtcp2.c b/lib/vquic/cf-ngtcp2.c index d347d52e50d8..1620725ea13b 100644 --- a/lib/vquic/cf-ngtcp2.c +++ b/lib/vquic/cf-ngtcp2.c @@ -1144,7 +1144,8 @@ CURLcode Curl_cf_ngtcp2_create(struct Curl_cfilter **pcf, CURLcode Curl_cf_ngtcp2_insert_after(struct Curl_cfilter *cf_at, struct Curl_peer *origin, - struct Curl_peer *peer) + struct Curl_peer *peer, + struct Curl_cfilter **pcf) { struct cf_ngtcp2_ctx *ctx = NULL; struct Curl_cfilter *cf = NULL; @@ -1170,6 +1171,7 @@ CURLcode Curl_cf_ngtcp2_insert_after(struct Curl_cfilter *cf_at, curlx_free(ctx); } } + *pcf = cf; return result; } diff --git a/lib/vquic/cf-ngtcp2.h b/lib/vquic/cf-ngtcp2.h index 601efc82245a..0ea7933590a2 100644 --- a/lib/vquic/cf-ngtcp2.h +++ b/lib/vquic/cf-ngtcp2.h @@ -56,8 +56,9 @@ CURLcode Curl_cf_ngtcp2_create(struct Curl_cfilter **pcf, struct Curl_sockaddr_ex *addr); CURLcode Curl_cf_ngtcp2_insert_after(struct Curl_cfilter *cf_at, - struct Curl_peer *origin, - struct Curl_peer *peer); + struct Curl_peer *origin, + struct Curl_peer *peer, + struct Curl_cfilter **pcf); #endif #endif /* HEADER_CURL_VQUIC_CF_NGTCP2_H */ diff --git a/lib/vquic/cf-quiche.c b/lib/vquic/cf-quiche.c index 303bbebd1081..c6456d17f885 100644 --- a/lib/vquic/cf-quiche.c +++ b/lib/vquic/cf-quiche.c @@ -1399,7 +1399,7 @@ static CURLcode cf_quiche_connect(struct Curl_cfilter *cf, } *done = FALSE; - if(Curl_ossl_need_httpsrr(data) && + if(Curl_ssl_need_httpsrr(cf) && !Curl_conn_dns_resolved_https(data, cf->sockindex, ctx->ssl_peer.peer)) { CURL_TRC_CF(data, cf, "need HTTPS-RR, delaying connect"); return CURLE_OK; @@ -1682,7 +1682,8 @@ CURLcode Curl_cf_quiche_create(struct Curl_cfilter **pcf, CURLcode Curl_cf_quiche_insert_after(struct Curl_cfilter *cf_at, struct Curl_peer *origin, - struct Curl_peer *peer) + struct Curl_peer *peer, + struct Curl_cfilter **pcf) { struct cf_quiche_ctx *ctx = NULL; struct Curl_cfilter *cf = NULL; @@ -1706,7 +1707,7 @@ CURLcode Curl_cf_quiche_insert_after(struct Curl_cfilter *cf_at, if(ctx) cf_quiche_ctx_free(ctx); } - + *pcf = cf; return result; } diff --git a/lib/vquic/cf-quiche.h b/lib/vquic/cf-quiche.h index 88d9161dd768..80068c9058cd 100644 --- a/lib/vquic/cf-quiche.h +++ b/lib/vquic/cf-quiche.h @@ -43,7 +43,8 @@ CURLcode Curl_cf_quiche_create(struct Curl_cfilter **pcf, struct Curl_sockaddr_ex *addr); CURLcode Curl_cf_quiche_insert_after(struct Curl_cfilter *cf_at, struct Curl_peer *origin, - struct Curl_peer *peer); + struct Curl_peer *peer, + struct Curl_cfilter **pcf); #endif diff --git a/lib/vquic/vquic.c b/lib/vquic/vquic.c index cdf97ac5bf74..fbca4b84b8b7 100644 --- a/lib/vquic/vquic.c +++ b/lib/vquic/vquic.c @@ -1091,14 +1091,16 @@ CURLcode Curl_cf_quic_insert_after(struct Curl_cfilter *cf_at, struct Curl_peer *origin, struct Curl_peer *peer) { + struct Curl_cfilter *cf = NULL; CURLcode result; (void)data; /* not used in all cases and compilers are stupid */ #if defined(USE_NGTCP2) && defined(USE_NGHTTP3) - result = Curl_cf_ngtcp2_insert_after(cf_at, origin, peer); + result = Curl_cf_ngtcp2_insert_after(cf_at, origin, peer, &cf); #elif defined(USE_QUICHE) - result = Curl_cf_quiche_insert_after(cf_at, origin, peer); + result = Curl_cf_quiche_insert_after(cf_at, origin, peer, &cf); #else + (void)cf) (void)cf_at; (void)origin; (void)peer; @@ -1108,12 +1110,9 @@ CURLcode Curl_cf_quic_insert_after(struct Curl_cfilter *cf_at, #if defined(USE_HTTPSRR) && defined(USE_ECH) /* When using ECH, kick off the HTTPS-RR resolve */ if(!result && (origin->scheme->family == CURLPROTO_HTTP) && - CURLECH_ENABLED(data) && - Curl_ssl_supports(data, SSLSUPP_ECH) && - (data->set.tls_ech != CURLECH_GREASE) && - !CURL_EASY_STR(data, STRING_ECH_CONFIG)) { - result = Curl_conn_dns_add_https_resolve(data, cf_at->conn, - cf_at->sockindex, origin); + Curl_ssl_need_httpsrr(cf)) { + result = Curl_conn_dns_add_https_resolve(data, cf->conn, + cf->sockindex, origin); } #endif /* USE_HTTPSRR && USE_ECH */ return result; diff --git a/lib/vtls/openssl.c b/lib/vtls/openssl.c index ace44ca954a7..27374e589e6e 100644 --- a/lib/vtls/openssl.c +++ b/lib/vtls/openssl.c @@ -3461,28 +3461,18 @@ static CURLcode ossl_init_session_and_alpns( } #ifdef HAVE_SSL_SET1_ECH_CONFIG_LIST -bool Curl_ossl_need_httpsrr(struct Curl_easy *data) -{ - if(!CURLECH_ENABLED(data)) - return FALSE; - if((data->set.tls_ech == CURLECH_GREASE) || - CURL_EASY_STR(data, STRING_ECH_CONFIG)) - return FALSE; - return TRUE; -} - static CURLcode ossl_init_ech(struct ossl_ctx *octx, struct Curl_cfilter *cf, struct Curl_easy *data, struct ssl_peer *peer) { - const char *outername = CURL_EASY_STR(data, STRING_ECH_PUBLIC); + struct ssl_filter_config *conn_config = Curl_ssl_cf_get_filter_config(cf); int trying_ech_now = 0; - if(!CURLECH_ENABLED(data)) + if(!Curl_ssl_ech_enabled(cf)) return CURLE_OK; - if(data->set.tls_ech == CURLECH_GREASE) { + if(conn_config->ech == CURLECH_GREASE) { infof(data, "ECH: will GREASE ClientHello"); #ifdef HAVE_BORINGSSL_LIKE SSL_set_enable_ech_grease(octx->ssl, 1); @@ -3490,28 +3480,27 @@ static CURLcode ossl_init_ech(struct ossl_ctx *octx, SSL_set_options(octx->ssl, SSL_OP_ECH_GREASE); #endif } - else if(data->set.tls_ech && CURL_EASY_STR(data, STRING_ECH_CONFIG)) { + else if(conn_config->ech && conn_config->ech_config) { #ifdef HAVE_BORINGSSL_LIKE - /* have to do base64 decode here for BoringSSL */ - const char *b64 = CURL_EASY_STR(data, STRING_ECH_CONFIG); uint8_t *ech_config; size_t ech_config_len = 0; CURLcode result; - if(!b64) { + if(!conn_config->ech_config[0]) { infof(data, "ECH: ECHConfig from command line empty"); return CURLE_SSL_CONNECT_ERROR; } - ech_config_len = 2 * strlen(b64); - result = curlx_base64_decode(b64, &ech_config, &ech_config_len); + ech_config_len = 2 * strlen(conn_config->ech_config); + result = curlx_base64_decode(conn_config->ech_config, + &ech_config, &ech_config_len); if(result || !ech_config) { infof(data, "ECH: cannot base64 decode ECHConfig from command line"); - if(data->set.tls_ech == CURLECH_HARD) + if(conn_config->ech == CURLECH_HARD) return result; } if(SSL_set1_ech_config_list(octx->ssl, ech_config, ech_config_len) != 1) { infof(data, "ECH: SSL_ECH_set1_ech_config_list failed"); - if(data->set.tls_ech == CURLECH_HARD) { + if(conn_config->ech == CURLECH_HARD) { curlx_free(ech_config); return CURLE_SSL_CONNECT_ERROR; } @@ -3519,18 +3508,17 @@ static CURLcode ossl_init_ech(struct ossl_ctx *octx, curlx_free(ech_config); trying_ech_now = 1; #else - const char *ech_config = CURL_EASY_STR(data, STRING_ECH_CONFIG); size_t ech_config_len = 0; - if(!ech_config) { + if(!conn_config->ech_config[0]) { infof(data, "ECH: ECHConfig from command line empty"); return CURLE_SSL_CONNECT_ERROR; } - ech_config_len = strlen(ech_config); + ech_config_len = strlen(conn_config->ech_config); if(SSL_set1_ech_config_list(octx->ssl, - (const uint8_t *)ech_config, + (const uint8_t *)conn_config->ech_config, ech_config_len) != 1) { infof(data, "ECH: SSL_ECH_set1_ech_config_list failed"); - if(data->set.tls_ech == CURLECH_HARD) + if(conn_config->ech == CURLECH_HARD) return CURLE_SSL_CONNECT_ERROR; } else @@ -3549,7 +3537,7 @@ static CURLcode ossl_init_ech(struct ossl_ctx *octx, infof(data, "ECH: ECHConfig from HTTPS RR"); if(SSL_set1_ech_config_list(octx->ssl, ecl, elen) != 1) { infof(data, "ECH: SSL_set1_ech_config_list failed"); - if(data->set.tls_ech == CURLECH_HARD) + if(conn_config->ech == CURLECH_HARD) return CURLE_SSL_CONNECT_ERROR; } else { @@ -3559,23 +3547,25 @@ static CURLcode ossl_init_ech(struct ossl_ctx *octx, } else { infof(data, "ECH: requested but no ECHConfig available"); - if(data->set.tls_ech == CURLECH_HARD) + if(conn_config->ech == CURLECH_HARD) return CURLE_SSL_CONNECT_ERROR; } } #ifdef HAVE_BORINGSSL_LIKE (void)peer; - if(trying_ech_now && outername) { + if(trying_ech_now && conn_config->ech_public) { infof(data, "ECH: setting public_name not supported with BoringSSL"); return CURLE_SSL_CONNECT_ERROR; } #else - if(trying_ech_now && outername) { + if(trying_ech_now && conn_config->ech_public) { int ret; infof(data, "ECH: inner: '%s', outer: '%s'", - peer->origin->hostname ? peer->origin->hostname : "NULL", outername); + peer->origin->hostname ? peer->origin->hostname : "NULL", + conn_config->ech_public); ret = SSL_ech_set1_server_names(octx->ssl, - peer->origin->hostname, outername, + peer->origin->hostname, + conn_config->ech_public, 0 /* do send outer */); if(ret != 1) { infof(data, "ECH: rv failed to set server name(s) %d [ERROR]", ret); @@ -3591,12 +3581,6 @@ static CURLcode ossl_init_ech(struct ossl_ctx *octx, return CURLE_OK; } -#else /* HAVE_SSL_SET1_ECH_CONFIG_LIST */ -bool Curl_ossl_need_httpsrr(struct Curl_easy *data) -{ - (void)data; - return FALSE; -} #endif /* else HAVE_SSL_SET1_ECH_CONFIG_LIST */ static CURLcode ossl_init_ssl(struct ossl_ctx *octx, @@ -4077,7 +4061,8 @@ static CURLcode ossl_connect_step1(struct Curl_cfilter *cf, #ifdef HAVE_SSL_SET1_ECH_CONFIG_LIST /* If we have retry configs, then trace those out */ -static int ossl_trace_ech_retry_configs(struct Curl_easy *data, SSL *ssl, +static int ossl_trace_ech_retry_configs(struct Curl_cfilter *cf, + struct Curl_easy *data, SSL *ssl, int reason) { CURLcode result = CURLE_OK; @@ -4097,7 +4082,7 @@ static int ossl_trace_ech_retry_configs(struct Curl_easy *data, SSL *ssl, NOVERBOSE((void)reason); /* nothing to trace if not doing ECH */ - if(!CURLECH_ENABLED(data)) + if(!Curl_ssl_ech_enabled(cf)) return rv; #ifndef HAVE_BORINGSSL_LIKE rv = SSL_ech_get1_retry_config(ssl, &rcs, &rcl); @@ -4143,13 +4128,13 @@ static int ossl_trace_ech_retry_configs(struct Curl_easy *data, SSL *ssl, static CURLcode ossl_connect_step2(struct Curl_cfilter *cf, struct Curl_easy *data) { - int err; struct ssl_connect_data *connssl = cf->ctx; struct ossl_ctx *octx = (struct ossl_ctx *)connssl->backend; struct ssl_easy_config *ssl_config = Curl_ssl_cf_get_easy_config(cf, data); + int err; + DEBUGASSERT(connssl->connecting_state == ssl_connect_2); DEBUGASSERT(octx); - connssl->io_need = CURL_SSL_IO_NEED_NONE; ERR_clear_error(); @@ -4262,10 +4247,10 @@ static CURLcode ossl_connect_step2(struct Curl_cfilter *cf, #endif /* HAVE_BORINGSSL_LIKE */ /* trace retry_configs if we got some */ - ossl_trace_ech_retry_configs(data, octx->ssl, reason); + ossl_trace_ech_retry_configs(cf, data, octx->ssl, reason); result = CURLE_ECH_REQUIRED; - failf(data, "ECH required: %s", + failf(data, "ECH: rejected, %s", ossl_strerror(errdetail, error_buffer, sizeof(error_buffer))); } #endif @@ -4301,7 +4286,9 @@ static CURLcode ossl_connect_step2(struct Curl_cfilter *cf, Curl_ossl_report_handshake(data, octx); #if defined(HAVE_SSL_SET1_ECH_CONFIG_LIST) && !defined(HAVE_BORINGSSL_LIKE) - if(CURLECH_ENABLED(data)) { + if(Curl_ssl_ech_enabled(cf)) { + struct ssl_filter_config *conn_config = + Curl_ssl_cf_get_filter_config(cf); char *inner = NULL, *outer = NULL; int rv; VERBOSE(const char *status); @@ -4333,8 +4320,6 @@ static CURLcode ossl_connect_step2(struct Curl_cfilter *cf, VERBOSE(status = "bad call (unexpected)"); break; case SSL_ECH_STATUS_BAD_NAME: { - struct ssl_filter_config *conn_config = - Curl_ssl_cf_get_filter_config(cf); if(!conn_config->verifypeer && !conn_config->verifyhost && inner && !strcmp(inner, connssl->peer.origin->hostname)) { VERBOSE(status = "bad name (tolerated without peer verification)"); @@ -4349,7 +4334,7 @@ static CURLcode ossl_connect_step2(struct Curl_cfilter *cf, VERBOSE(status = "unexpected status"); infof(data, "ECH: unexpected status %d", rv); } - infof(data, "ECH: result: status is %s, inner is %s, outer is %s", + infof(data, "ECH: result '%s' (inner=%s, outer=%s)", (status ? status : "NULL"), (inner ? inner : "NULL"), (outer ? outer : "NULL")); @@ -4357,9 +4342,9 @@ static CURLcode ossl_connect_step2(struct Curl_cfilter *cf, OPENSSL_free(outer); if(rv == SSL_ECH_STATUS_GREASE_ECH) { /* trace retry_configs if we got some */ - ossl_trace_ech_retry_configs(data, octx->ssl, 0); + ossl_trace_ech_retry_configs(cf, data, octx->ssl, 0); } - if(rv != SSL_ECH_STATUS_SUCCESS && (data->set.tls_ech == CURLECH_HARD)) { + if(rv != SSL_ECH_STATUS_SUCCESS && (conn_config->ech == CURLECH_HARD)) { infof(data, "ECH: ech-hard failed"); return CURLE_SSL_CONNECT_ERROR; } @@ -4997,7 +4982,7 @@ static CURLcode ossl_connect(struct Curl_cfilter *cf, connssl->io_need = CURL_SSL_IO_NEED_NONE; if(connssl->connecting_state == ssl_connect_1) { - if(Curl_ossl_need_httpsrr(data) && + if(Curl_ssl_need_httpsrr(cf) && !Curl_conn_dns_resolved_https(data, cf->sockindex, connssl->peer.peer)) { CURL_TRC_CF(data, cf, "need HTTPS-RR, delaying connect"); diff --git a/lib/vtls/openssl.h b/lib/vtls/openssl.h index dfac78c21e57..f1e914cb498c 100644 --- a/lib/vtls/openssl.h +++ b/lib/vtls/openssl.h @@ -159,9 +159,6 @@ CURLcode Curl_ossl_ctx_init(struct ossl_ctx *octx, void *ssl_user_data, Curl_ossl_init_session_reuse_cb *sess_reuse_cb); -/* Is a resolved HTTPS-RR needed for initializing OpenSSL? */ -bool Curl_ossl_need_httpsrr(struct Curl_easy *data); - #ifndef HAVE_OPENSSL3 #define SSL_get1_peer_certificate SSL_get_peer_certificate #endif diff --git a/lib/vtls/rustls.c b/lib/vtls/rustls.c index 3373b703bc08..bdcfa761b9d7 100644 --- a/lib/vtls/rustls.c +++ b/lib/vtls/rustls.c @@ -528,7 +528,8 @@ static void cr_keylog_log_cb(struct rustls_str label, } static CURLcode -init_config_builder(struct Curl_easy *data, +init_config_builder(struct Curl_cfilter *cf, + struct Curl_easy *data, const struct ssl_filter_config *conn_config, struct rustls_client_config_builder **config_builder) { @@ -583,11 +584,13 @@ init_config_builder(struct Curl_easy *data, } #ifdef USE_ECH - if(CURLECH_ENABLED(data)) { + if(Curl_ssl_ech_enabled(cf)) { tls_versions[0] = RUSTLS_TLS_VERSION_TLSV1_3; tls_versions_len = 1; infof(data, "rustls: ECH enabled, forcing TLSv1.3"); } +#else + (void)cf; #endif /* USE_ECH */ cipher_suites = curlx_malloc(sizeof(*cipher_suites) * cipher_suites_len); @@ -920,21 +923,12 @@ init_config_builder_client_auth(struct Curl_easy *data, #ifdef USE_ECH -static bool cr_ech_need_httpsrr(struct Curl_easy *data) -{ - if(!CURLECH_ENABLED(data)) - return FALSE; - if((data->set.tls_ech == CURLECH_GREASE) || - CURL_EASY_STR(data, STRING_ECH_CONFIG)) - return FALSE; - return TRUE; -} - static CURLcode init_config_builder_ech(struct Curl_easy *data, struct Curl_cfilter *cf, struct rustls_client_config_builder *builder) { + struct ssl_filter_config *conn_config = Curl_ssl_cf_get_filter_config(cf); const rustls_hpke *hpke = rustls_supported_hpke(); unsigned char *ech_config = NULL; size_t ech_config_len = 0; @@ -950,13 +944,13 @@ init_config_builder_ech(struct Curl_easy *data, goto cleanup; } - if(CURL_EASY_STR(data, STRING_ECH_PUBLIC)) { + if(conn_config->ech_public) { failf(data, "rustls: ECH outername not supported"); result = CURLE_SSL_CONNECT_ERROR; goto cleanup; } - if(data->set.tls_ech == CURLECH_GREASE) { + if(conn_config->ech == CURLECH_GREASE) { rr = rustls_client_config_builder_enable_ech_grease(builder, hpke); if(rr != RUSTLS_RESULT_OK) { rustls_failf(data, rr, "rustls: failed to configure ECH GREASE"); @@ -966,10 +960,10 @@ init_config_builder_ech(struct Curl_easy *data, return CURLE_OK; } - if(data->set.tls_ech && CURL_EASY_STR(data, STRING_ECH_CONFIG)) { - const char *b64 = CURL_EASY_STR(data, STRING_ECH_CONFIG); + if(conn_config->ech && conn_config->ech_config) { + const char *b64 = conn_config->ech_config; size_t decode_result; - if(!b64) { + if(!b64[0]) { infof(data, "rustls: ECHConfig from command line empty"); result = CURLE_SSL_CONNECT_ERROR; goto cleanup; @@ -1008,7 +1002,7 @@ init_config_builder_ech(struct Curl_easy *data, } cleanup: /* if we base64 decoded, we can free now */ - if(data->set.tls_ech && CURL_EASY_STR(data, STRING_ECH_CONFIG)) { + if(conn_config->ech && conn_config->ech_config) { curlx_free(ech_config); } if(dns) { @@ -1038,7 +1032,7 @@ static CURLcode cr_init_backend(struct Curl_cfilter *cf, DEBUGASSERT(backend); rconn = backend->conn; - result = init_config_builder(data, conn_config, &config_builder); + result = init_config_builder(cf, data, conn_config, &config_builder); if(result != CURLE_OK) { return result; } @@ -1087,9 +1081,9 @@ static CURLcode cr_init_backend(struct Curl_cfilter *cf, } #ifdef USE_ECH - if(CURLECH_ENABLED(data)) { + if(Curl_ssl_ech_enabled(cf)) { result = init_config_builder_ech(data, cf, config_builder); - if((result != CURLE_OK) && (data->set.tls_ech == CURLECH_HARD)) { + if((result != CURLE_OK) && (conn_config->ech == CURLECH_HARD)) { rustls_client_config_builder_free(config_builder); return result; } @@ -1163,7 +1157,7 @@ static CURLcode cr_connect(struct Curl_cfilter *cf, struct Curl_easy *data, #ifdef USE_ECH /* if we do ECH and need the HTTPS-RR information for it, * we delay the connect until it arrives or DNS resolve fails. */ - if(cr_ech_need_httpsrr(data) && + if(Curl_ssl_need_httpsrr(cf) && !Curl_conn_dns_resolved_https(data, cf->sockindex, connssl->peer.peer)) { CURL_TRC_CF(data, cf, "need HTTPS-RR for ECH, delaying connect"); diff --git a/lib/vtls/vtls.c b/lib/vtls/vtls.c index 9d95ebb020dc..9737319b8b6d 100644 --- a/lib/vtls/vtls.c +++ b/lib/vtls/vtls.c @@ -1429,10 +1429,7 @@ CURLcode Curl_cf_ssl_insert_after(struct Curl_cfilter *cf_at, #if defined(USE_HTTPSRR) && defined(USE_ECH) /* When using ECH, kick off the HTTPS-RR resolve */ if((origin->scheme->family == CURLPROTO_HTTP) && - CURLECH_ENABLED(data) && - Curl_ssl_supports(data, SSLSUPP_ECH) && - (data->set.tls_ech != CURLECH_GREASE) && - !CURL_EASY_STR(data, STRING_ECH_CONFIG)) { + Curl_ssl_need_httpsrr(cf)) { result = Curl_conn_dns_add_https_resolve(data, cf->conn, cf->sockindex, origin); } @@ -1503,6 +1500,30 @@ bool Curl_ssl_supports(struct Curl_easy *data, unsigned int ssl_option) return (Curl_ssl->supports & ssl_option); } +#ifdef USE_ECH +bool Curl_ssl_ech_enabled(struct Curl_cfilter *cf) +{ + if(Curl_ssl->supports & SSLSUPP_ECH) { + struct ssl_filter_config *conn_config = Curl_ssl_cf_get_filter_config(cf); + return !!conn_config->ech; + } + return FALSE; +} + +bool Curl_ssl_need_httpsrr(struct Curl_cfilter *cf) +{ + if(Curl_ssl->supports & SSLSUPP_ECH) { + struct ssl_filter_config *conn_config = Curl_ssl_cf_get_filter_config(cf); + if(!conn_config->ech) + return FALSE; + if((conn_config->ech == CURLECH_GREASE) || conn_config->ech_config) + return FALSE; + return TRUE; + } + return FALSE; +} +#endif + static CURLcode vtls_shutdown_blocking(struct Curl_cfilter *cf, struct Curl_easy *data, bool send_shutdown, bool *done) diff --git a/lib/vtls/vtls.h b/lib/vtls/vtls.h index 8aed5ddb6b59..b7fd4cebce75 100644 --- a/lib/vtls/vtls.h +++ b/lib/vtls/vtls.h @@ -49,13 +49,19 @@ struct dynbuf; #define SSLSUPP_ISSUERCERT_BLOB (1 << 14) /* CURLOPT_ISSUERCERT_BLOB */ #ifdef USE_ECH -/* CURLECH_ values for the tls_ech option */ +/* CURLECH_ values for the ech option */ #define CURLECH_DISABLE 0 #define CURLECH_GREASE 1 #define CURLECH_ENABLE 2 #define CURLECH_HARD 3 -#define CURLECH_ENABLED(data) ((data)->set.tls_ech) +/* TRUE if ECH is enabled (and supported) */ +bool Curl_ssl_ech_enabled(struct Curl_cfilter *cf); +/* TRUE if SSL ECH configuration requires DNS HTTPS-RR record */ +bool Curl_ssl_need_httpsrr(struct Curl_cfilter *cf); +#else +#define Curl_ssl_ech_enabled(c) FALSE +#define Curl_ssl_need_httpsrr(c) FALSE #endif /* USE_ECH */ #define ALPN_ACCEPTED "ALPN: server accepted " diff --git a/lib/vtls/vtls_config.c b/lib/vtls/vtls_config.c index df0372366c86..aae321227d0f 100644 --- a/lib/vtls/vtls_config.c +++ b/lib/vtls/vtls_config.c @@ -129,6 +129,10 @@ void Curl_ssl_config_cleanup(struct ssl_filter_config *sslc) curlx_safefree(sslc->key); curlx_safefree(sslc->key_type); curlx_safefree(sslc->key_passwd); +#ifdef USE_ECH + curlx_safefree(sslc->ech_config); + curlx_safefree(sslc->ech_public); +#endif sslc->deep_copy = FALSE; } } @@ -146,6 +150,11 @@ static bool match_ssl_primary_config(struct Curl_easy *data, (c1->verifyhost == c2->verifyhost) && (c1->verifystatus == c2->verifystatus) && (c1->auto_client_cert == c2->auto_client_cert) && +#ifdef USE_ECH + (c1->ech == c2->ech) && + Curl_safecmp(c1->ech_config, c2->ech_config) && + Curl_safecmp(c1->ech_public, c2->ech_public) && +#endif blobcmp(c1->cert_blob, c2->cert_blob) && blobcmp(c1->ca_info_blob, c2->ca_info_blob) && blobcmp(c1->issuercert_blob, c2->issuercert_blob) && @@ -224,6 +233,11 @@ static bool clone_ssl_primary_config(struct ssl_filter_config *source, CLONE_STRING(key_type); CLONE_STRING(key_passwd); CLONE_BLOB(key_blob); +#ifdef USE_ECH + dest->ech = source->ech; + CLONE_STRING(ech_config); + CLONE_STRING(ech_public); +#endif return TRUE; } @@ -320,6 +334,11 @@ CURLcode Curl_ssl_filter_config_tmp_init( ssl_origin->key_passwd = ssl_easy_steal(data, STRING_KEY_PASSWD); ssl_origin->clientcert = ssl_easy_steal(data, STRING_CERT); ssl_origin->key_blob = data->set.blobs[BLOB_KEY]; +#ifdef USE_ECH + ssl_origin->ech = data->set.ssl.ech; + ssl_origin->ech_config = ssl_easy_steal(data, STRING_ECH_CONFIG); + ssl_origin->ech_public = ssl_easy_steal(data, STRING_ECH_PUBLIC); +#endif } else { ssl_origin->pinned_key = NULL; @@ -330,6 +349,11 @@ CURLcode Curl_ssl_filter_config_tmp_init( ssl_origin->key_passwd = NULL; ssl_origin->clientcert = NULL; ssl_origin->key_blob = NULL; +#ifdef USE_ECH + ssl_origin->ech = 0; + ssl_origin->ech_config = NULL; + ssl_origin->ech_public = NULL; +#endif } #ifndef CURL_DISABLE_PROXY @@ -386,6 +410,12 @@ CURLcode Curl_ssl_filter_config_tmp_init( ssl_proxy->key_passwd = ssl_easy_steal(data, STRING_KEY_PASSWD_PROXY); ssl_proxy->clientcert = ssl_easy_steal(data, STRING_CERT_PROXY); ssl_proxy->key_blob = data->set.blobs[BLOB_KEY_PROXY]; +#ifdef USE_ECH + /* Not configurable via a CURLOPT_* */ + ssl_proxy->ech = 0; + ssl_proxy->ech_config = NULL; + ssl_proxy->ech_public = NULL; +#endif #else (void)ssl_proxy; #endif /* CURL_DISABLE_PROXY */ @@ -437,5 +467,8 @@ void Curl_ssl_conn_config_update(struct Curl_easy *data, bool for_proxy) dest->verifyhost = src->verifyhost; dest->verifypeer = src->verifypeer; dest->verifystatus = src->verifystatus; +#ifdef USE_ECH + dest->ech = src->ech; +#endif } } diff --git a/lib/vtls/vtls_config.h b/lib/vtls/vtls_config.h index 5de66530e616..cf4263fce879 100644 --- a/lib/vtls/vtls_config.h +++ b/lib/vtls/vtls_config.h @@ -43,6 +43,10 @@ struct ssl_filter_config { char *key; /* private key filename */ char *key_type; /* format for private key (default: PEM) */ char *key_passwd; /* plain text private key password */ +#ifdef USE_ECH + char *ech_config; /* TLS ECH configuration, base64 string */ + char *ech_public; /* TLS ECH public DNS (SNI in outer ClientHello) */ +#endif struct curl_blob *cert_blob; struct curl_blob *ca_info_blob; struct curl_blob *issuercert_blob; @@ -51,6 +55,9 @@ struct ssl_filter_config { uint32_t version_max; /* max supported version the client wants to use */ uint8_t ssl_options; /* the CURLOPT_SSL_OPTIONS bitmask */ uint8_t version; /* what version the client wants to use */ +#ifdef USE_ECH + uint8_t ech; /* TLS ECH CURLECH_((DIS|EN)ABLE|GREASE) */ +#endif BIT(verifypeer); /* set TRUE if this is desired */ BIT(verifyhost); /* set TRUE if CN/SAN must match hostname */ BIT(verifystatus); /* set TRUE if certificate status must be checked */ @@ -72,6 +79,9 @@ struct ssl_easy_config { uint32_t version_max; /* max supported version the client wants to use */ uint8_t version; /* what version the client wants to use */ uint8_t ssl_options; /* the CURLOPT_SSL_OPTIONS bitmask */ +#ifdef USE_ECH + uint8_t ech; /* TLS ECH CURLECH_((DIS|EN)ABLE|GREASE) */ +#endif BIT(verifypeer); /* set TRUE if this is desired */ BIT(verifyhost); /* set TRUE if CN/SAN must match hostname */ BIT(verifystatus); /* set TRUE if certificate status must be checked */ diff --git a/lib/vtls/wolfssl.c b/lib/vtls/wolfssl.c index 1071a1fb0b84..f3bea53879b8 100644 --- a/lib/vtls/wolfssl.c +++ b/lib/vtls/wolfssl.c @@ -1247,25 +1247,25 @@ static CURLcode wssl_init_ech(struct wssl_ctx *wctx, struct Curl_easy *data, struct ssl_peer *peer) { + struct ssl_filter_config *conn_config = Curl_ssl_cf_get_filter_config(cf); int trying_ech_now = 0; - if(CURL_EASY_STR(data, STRING_ECH_PUBLIC)) { - infof(data, "ECH: outername not (yet) supported" - " with wolfSSL"); + if(conn_config->ech_public) { + infof(data, "ECH: outername not supported with wolfSSL"); return CURLE_SSL_CONNECT_ERROR; } - if(data->set.tls_ech == CURLECH_GREASE) { - infof(data, "ECH: GREASE is done by default by" - " wolfSSL: no need to ask"); + + if(conn_config->ech == CURLECH_GREASE) { + infof(data, "ECH: GREASE is done by default by wolfSSL: no need to ask"); } - if(data->set.tls_ech && CURL_EASY_STR(data, STRING_ECH_CONFIG)) { - const char *b64val = CURL_EASY_STR(data, STRING_ECH_CONFIG); + else if(conn_config->ech && conn_config->ech_config) { + const char *b64val = conn_config->ech_config; word32 b64len = 0; b64len = (word32)strlen(b64val); if(b64len && wolfSSL_SetEchConfigsBase64(wctx->ssl, CURL_UNCONST(b64val), b64len) != WOLFSSL_SUCCESS) { - if(data->set.tls_ech == CURLECH_HARD) + if(conn_config->ech == CURLECH_HARD) return CURLE_SSL_CONNECT_ERROR; } else { @@ -1285,7 +1285,7 @@ static CURLcode wssl_init_ech(struct wssl_ctx *wctx, if(wolfSSL_SetEchConfigs(wctx->ssl, ecl, (word32)elen) != WOLFSSL_SUCCESS) { infof(data, "ECH: wolfSSL_SetEchConfigs failed"); - if(data->set.tls_ech == CURLECH_HARD) { + if(conn_config->ech == CURLECH_HARD) { return CURLE_SSL_CONNECT_ERROR; } } @@ -1296,7 +1296,7 @@ static CURLcode wssl_init_ech(struct wssl_ctx *wctx, } else { infof(data, "ECH: requested but no ECHConfig available"); - if(data->set.tls_ech == CURLECH_HARD) { + if(conn_config->ech == CURLECH_HARD) { return CURLE_SSL_CONNECT_ERROR; } } @@ -1438,7 +1438,7 @@ CURLcode Curl_wssl_ctx_init(struct wssl_ctx *wctx, goto out; #ifdef HAVE_WOLFSSL_CTX_GENERATEECHCONFIG - if(CURLECH_ENABLED(data)) { + if(Curl_ssl_ech_enabled(cf)) { result = wssl_init_ech(wctx, cf, data, peer); if(result) goto out; @@ -1459,21 +1459,6 @@ CURLcode Curl_wssl_ctx_init(struct wssl_ctx *wctx, return result; } -bool Curl_wssl_need_httpsrr(struct Curl_easy *data) -{ -#ifdef HAVE_WOLFSSL_CTX_GENERATEECHCONFIG - if(!CURLECH_ENABLED(data)) - return FALSE; - if((data->set.tls_ech == CURLECH_GREASE) || - CURL_EASY_STR(data, STRING_ECH_CONFIG)) - return FALSE; - return TRUE; -#else - (void)data; - return FALSE; -#endif -} - /* * This function loads all the client/CA certificates and CRLs. Setup the TLS * layer and do all necessary magic. @@ -2149,7 +2134,7 @@ static CURLcode wssl_connect(struct Curl_cfilter *cf, #ifdef HAVE_WOLFSSL_CTX_GENERATEECHCONFIG /* if we do ECH and need the HTTPS-RR information for it, * we delay the connect until it arrives or DNS resolve fails. */ - if(Curl_wssl_need_httpsrr(data) && + if(Curl_ssl_need_httpsrr(cf) && !Curl_conn_dns_resolved_https(data, cf->sockindex, connssl->peer.peer)) { CURL_TRC_CF(data, cf, "need HTTPS-RR for ECH, delaying connect"); diff --git a/lib/vtls/wolfssl.h b/lib/vtls/wolfssl.h index 2016a0069f0e..17dd81644fcb 100644 --- a/lib/vtls/wolfssl.h +++ b/lib/vtls/wolfssl.h @@ -73,9 +73,6 @@ CURLcode Curl_wssl_ctx_init(struct wssl_ctx *wctx, void *ssl_user_data, Curl_wssl_init_session_reuse_cb *sess_reuse_cb); -/* Is a resolved HTTPS-RR needed for initializing wolfSSL? */ -bool Curl_wssl_need_httpsrr(struct Curl_easy *data); - CURLcode Curl_wssl_setup_x509_store(struct Curl_cfilter *cf, struct Curl_easy *data, struct wssl_ctx *wssl); diff --git a/src/config2setopts.c b/src/config2setopts.c index 477f15ac7e77..ed620e4fdc8e 100644 --- a/src/config2setopts.c +++ b/src/config2setopts.c @@ -508,12 +508,12 @@ static CURLcode ssl_setopts(struct OperationConfig *config, CURL *curl) if(feature_ech) { /* only if enabled in libcurl */ - if(config->ech) /* only if set (optional) */ - MY_SETOPT_STR(curl, CURLOPT_ECH, config->ech); if(config->ech_public) /* only if set (optional) */ MY_SETOPT_STR(curl, CURLOPT_ECH, config->ech_public); if(config->ech_config) /* only if set (optional) */ MY_SETOPT_STR(curl, CURLOPT_ECH, config->ech_config); + if(config->ech) /* only if set (optional) */ + MY_SETOPT_STR(curl, CURLOPT_ECH, config->ech); } if(config->engine) diff --git a/tests/http/Makefile.am b/tests/http/Makefile.am index 4f2fad7d02d7..40f564051c8f 100644 --- a/tests/http/Makefile.am +++ b/tests/http/Makefile.am @@ -68,6 +68,7 @@ EXTRA_DIST = \ test_20_websockets.py \ test_21_resolve.py \ test_22_httpsrr.py \ + test_23_ech.py \ test_30_vsftpd.py \ test_31_vsftpds.py \ test_32_ftps_vsftpd.py \ diff --git a/tests/http/conftest.py b/tests/http/conftest.py index 21f633785e7b..2805c650d6b3 100644 --- a/tests/http/conftest.py +++ b/tests/http/conftest.py @@ -33,7 +33,7 @@ sys.path.append(os.path.join(os.path.dirname(__file__), ".")) -from testenv import Env, Httpd, Nghttpx, NghttpxFwd, NghttpxQuic, Sshd +from testenv import Env, Httpd, Nghttpx, NghttpxFwd, NghttpxQuic, NghttpxTcp, Sshd from testenv.h2o import H2oProxy, H2oServer log = logging.getLogger(__name__) @@ -117,6 +117,18 @@ def nghttpx(env, httpd) -> Generator[Union[Nghttpx, bool], None, None]: yield False +@pytest.fixture(scope="session") +def nghttpx_tcp(env, httpd) -> Generator[Union[Nghttpx, bool], None, None]: + nghttpx = NghttpxTcp(env=env) + if nghttpx.exists(): + nghttpx.clear_logs() + assert nghttpx.initial_start() + yield nghttpx + nghttpx.stop() + else: + yield False + + @pytest.fixture(scope="session") def nghttpx_fwd(env, httpd) -> Generator[Union[Nghttpx, bool], None, None]: nghttpx = NghttpxFwd(env=env) diff --git a/tests/http/test_23_ech.py b/tests/http/test_23_ech.py new file mode 100644 index 000000000000..c436f069ea7c --- /dev/null +++ b/tests/http/test_23_ech.py @@ -0,0 +1,178 @@ +#*************************************************************************** +# _ _ ____ _ +# Project ___| | | | _ \| | +# / __| | | | |_) | | +# | (__| |_| | _ <| |___ +# \___|\___/|_| \_\_____| +# +# Copyright (C) Daniel Stenberg, , et al. +# +# This software is licensed as described in the file COPYING, which +# you should have received as part of this distribution. The terms +# are also available at https://curl.se/docs/copyright.html. +# +# You may opt to use, copy, modify, merge, publish, distribute and/or sell +# copies of the Software, and permit persons to whom the Software is +# furnished to do so, under the terms of the COPYING file. +# +# This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY +# KIND, either express or implied. +# +# SPDX-License-Identifier: curl +# +########################################################################### +# +import logging +import os +import re + +import pytest +from testenv import CurlClient, Env + +log = logging.getLogger(__name__) + + +@pytest.mark.skipif(condition=not Env.curl_has_feature('ECH'), reason="no curl ECH support") +@pytest.mark.skipif(condition=not Env.have_nghttpx_ech(), reason="no nghttpx ECH support") +@pytest.mark.skipif(condition=not Env.have_openssl_ech(), reason="no openssl cmd ECH support") +@pytest.mark.skipif(condition=not Env.curl_is_debug(), reason="needs curl debug") +class TestECH: + + @pytest.mark.parametrize("ech_mode, exp_result, exp_exit", [ + ['false', None, 0], + ['grease', 'sent GREASE', 0], + ['true', 'not configured', 0], + ['hard', None, 35] # CURLE_SSL_CONNECT_ERROR + ]) + def test_23_01_ech_no_config(self, env: Env, httpd, nghttpx_tcp, ech_mode, exp_result, exp_exit): + run_env = os.environ.copy() + curl = CurlClient(env=env, run_env=run_env) + url = f'https://{env.domain1}:{nghttpx_tcp.port}/data.json' + r = curl.http_download(urls=[url], with_stats=True, extra_args=[ + '--ech', ech_mode + ]) + r.check_exit_code(exp_exit) + if exp_exit == 0: + r.check_stats(count=1, http_status=200, exitcode=0) + if env.curl_uses_lib('openssl'): + ech_result, _, _ = self._get_ech_result(r) + assert ech_result == exp_result, f'{r.dump_logs()}' + + @pytest.mark.parametrize("ech_mode, exp_result, exp_exit", [ + ['false', None, 0], + ['grease', 'sent GREASE', 0], + ['true', 'succeeded', 0], + ['hard', 'succeeded', 0] + ]) + def test_23_02_ech_good_config(self, env: Env, httpd, nghttpx_tcp, ech_mode, exp_result, exp_exit): + run_env = os.environ.copy() + curl = CurlClient(env=env, run_env=run_env) + url = f'https://{env.domain1}:{nghttpx_tcp.port}/data.json' + ech_config = env.get_echconfig_arg(env.domain1) + r = curl.http_download(urls=[url], with_stats=True, extra_args=[ + '--ech', ech_mode, '--ech', f'ecl:{ech_config}' + ]) + r.check_exit_code(exp_exit) + if exp_exit == 0: + r.check_stats(count=1, http_status=200, exitcode=0) + if env.curl_uses_lib('openssl'): + ech_result, inner, outer = self._get_ech_result(r) + assert ech_result == exp_result, f'{r.dump_logs()}' + if ech_result == 'succeeded': + assert inner == env.domain1, f'{r.dump_logs()}' + assert outer == f'public.{env.domain1}', f'{r.dump_logs()}' + + @pytest.mark.parametrize("ech_mode, exp_result, exp_exit", [ + ['false', None, 0], + ['grease', 'sent GREASE', 0], + ['true', 'rejected', 101], # CURLE_ECH_REQUIRED + ['hard', 'rejected', 101] + ]) + def test_23_03_ech_bad_config(self, env: Env, httpd, nghttpx_tcp, ech_mode, exp_result, exp_exit): + run_env = os.environ.copy() + curl = CurlClient(env=env, run_env=run_env) + url = f'https://{env.domain1}:{nghttpx_tcp.port}/data.json' + ech_config = env.get_echconfig_arg(env.domain2) + r = curl.http_download(urls=[url], with_stats=True, extra_args=[ + '--ech', ech_mode, '--ech', f'ecl:{ech_config}' + ]) + if env.curl_uses_lib('wolfssl'): + # wolfssl ECH errors result in CURLE_PEER_FAILED_VERIFICATION + exp_exit = 60 if exp_exit else 0 + elif env.curl_uses_lib('rustls-ffi'): + # rustls ECH errors result in CURLE_SSL_CONNECT_ERROR + exp_exit = 35 if exp_exit else 0 + r.check_exit_code(exp_exit) + if env.curl_uses_lib('openssl'): + ech_result, _, _ = self._get_ech_result(r) + assert ech_result == exp_result, f'{r.dump_logs()}' + + @pytest.mark.skipif(condition=not Env.curl_uses_lib('openssl'), + reason="needs OpenSSL") + @pytest.mark.parametrize("ech_mode, exp_result, pub_domain", [ + ['true', 'succeeded', 'innocent.invalid'], + ['hard', 'succeeded', 'innocent.invalid'], + ]) + def test_23_04_ech_outer(self, env: Env, httpd, nghttpx_tcp, ech_mode, exp_result, pub_domain): + run_env = os.environ.copy() + curl = CurlClient(env=env, run_env=run_env) + url = f'https://{env.domain1}:{nghttpx_tcp.port}/data.json' + ech_config = env.get_echconfig_arg(env.domain1) + r = curl.http_download(urls=[url], with_stats=True, extra_args=[ + '--ech', ech_mode, '--ech', f'ecl:{ech_config}', '--ech', f'pn:{pub_domain}' + ]) + r.check_exit_code(0) + r.check_stats(count=1, http_status=200, exitcode=0) + ech_result, inner, outer = self._get_ech_result(r) + assert ech_result == exp_result, f'{r.dump_logs()}' + assert inner == env.domain1, f'{r.dump_logs()}' + assert outer == pub_domain, f'{r.dump_logs()}' + + def test_23_05_ech_conn_ech_reuse(self, env: Env, httpd, nghttpx_tcp): + run_env = os.environ.copy() + curl = CurlClient(env=env, run_env=run_env) + url1 = f'https://{env.domain1}:{nghttpx_tcp.port}/data1.json' + url2 = f'https://{env.domain1}:{nghttpx_tcp.port}/data2.json' + r = curl.http_download(urls=[url1, url2], with_stats=True, url_options={ + url1: ['--ech', 'false'], + url2: ['--ech', 'true'] + }) + r.check_exit_code(0), f'{r}' + r.check_response(http_status=404, count=2, connect_count=2) + + def test_23_06_ech_conn_ech_config_reuse(self, env: Env, httpd, nghttpx_tcp): + run_env = os.environ.copy() + curl = CurlClient(env=env, run_env=run_env) + url1 = f'https://{env.domain1}:{nghttpx_tcp.port}/data1.json' + url2 = f'https://{env.domain1}:{nghttpx_tcp.port}/data2.json' + ech_config = env.get_echconfig_arg(env.domain1) + r = curl.http_download(urls=[url1, url2], with_stats=True, url_options={ + url1: ['--ech', 'true'], + url2: ['--ech', 'true', '--ech', f'ecl:{ech_config}'] + }) + r.check_exit_code(0), f'{r}' + r.check_response(http_status=404, count=2, connect_count=2) + + @pytest.mark.skipif(condition=not Env.curl_uses_lib('openssl'), + reason="needs OpenSSL") + def test_23_07_ech_conn_ech_pub_reuse(self, env: Env, httpd, nghttpx_tcp): + run_env = os.environ.copy() + curl = CurlClient(env=env, run_env=run_env) + url1 = f'https://{env.domain1}:{nghttpx_tcp.port}/data1.json' + url2 = f'https://{env.domain1}:{nghttpx_tcp.port}/data2.json' + r = curl.http_download(urls=[url1, url2], with_stats=True, url_options={ + url1: ['--ech', 'true'], + url2: ['--ech', 'true', '--ech', 'pn:innocent.invalid'] + }) + r.check_exit_code(0), f'{r}' + r.check_response(http_status=404, count=2, connect_count=2) + + def _get_ech_result(self, r): + for line in r.trace_lines: + m = re.search(r'ECH: result \'(.+)\' \(inner=(.+), outer=(.+)\)', line) + if m: + return m.group(1), m.group(2), m.group(3) + m = re.search(r'ECH: rejected(.*)', line) + if m: + return 'rejected', None, None + return None, None, None diff --git a/tests/http/testenv/__init__.py b/tests/http/testenv/__init__.py index ae83d681abad..4a34354e564d 100644 --- a/tests/http/testenv/__init__.py +++ b/tests/http/testenv/__init__.py @@ -38,6 +38,6 @@ from .env import Env from .h2o import H2oProxy, H2oServer from .httpd import Httpd -from .nghttpx import Nghttpx, NghttpxFwd, NghttpxQuic +from .nghttpx import Nghttpx, NghttpxFwd, NghttpxQuic, NghttpxTcp from .sshd import Sshd from .vsftpd import VsFTPD diff --git a/tests/http/testenv/env.py b/tests/http/testenv/env.py index 63d48c9de35d..c11d55669957 100644 --- a/tests/http/testenv/env.py +++ b/tests/http/testenv/env.py @@ -74,6 +74,7 @@ def init_config_from(conf_path): class NghttpxUtil: CMD = None VERSION_FULL = None + CAN_ECH = None @classmethod def version(cls, cmd): @@ -93,6 +94,20 @@ def version(cls, cmd): def version_with_h3(version): return re.match(r".* ngtcp2/\d+\.\d+\.\d+.*", version) is not None + @classmethod + def can_ech(cls, cmd): + if cmd is None: + return None + if cls.CAN_ECH is None or cmd != cls.CMD: + p = subprocess.run(args=[cmd, "-h"], capture_output=True, text=True, check=True) + cls.CMD = cmd + cls.CAN_ECH = False + for line in p.stdout.splitlines(keepends=False): + if re.search(r'--ech-config-file', line): + cls.CAN_ECH = True + break + return cls.CAN_ECH + class EnvConfig: def __init__( @@ -230,7 +245,11 @@ def __init__( ), ] - self.openssl = "openssl" + if "OPENSSL" in os.environ: + self.openssl = os.environ["OPENSSL"] + else: + self.openssl = "openssl" + self.openssl_ech = False p = subprocess.run( args=[self.openssl, "version"], capture_output=True, text=True, check=False ) @@ -240,15 +259,20 @@ def __init__( self.openssl_version = None else: self.openssl_version = p.stdout.strip() + m = re.match(r'OpenSSL (\d+).*', self.openssl_version) + if m and int(m.group(1)) >= 4: + self.openssl_ech = True self.nghttpx = self.config["nghttpx"]["nghttpx"] if len(self.nghttpx.strip()) == 0: self.nghttpx = None + self.nghttpx_ech = False self._nghttpx_version = None self.nghttpx_with_h3 = False if self.nghttpx is not None: self._nghttpx_version = NghttpxUtil.version(self.nghttpx) self.nghttpx_with_h3 = NghttpxUtil.version_with_h3(self._nghttpx_version) + self.nghttpx_ech = NghttpxUtil.can_ech(self.nghttpx) self.caddy = self.config["caddy"]["caddy"] self._caddy_version = None @@ -478,6 +502,14 @@ def have_nghttpx(cls) -> bool: def have_h3_server(cls) -> bool: return cls.CONFIG.nghttpx_with_h3 + @staticmethod + def have_openssl_ech() -> bool: + return Env.CONFIG.openssl_ech + + @staticmethod + def have_nghttpx_ech() -> bool: + return Env.CONFIG.nghttpx_ech + @classmethod def have_h2o(cls) -> bool: return cls.CONFIG.h2o is not None @@ -722,6 +754,15 @@ def issue_certs(self): self._ca.issue_certs(self.CONFIG.cert_specs) if self.have_openssl(): self._ca.create_hashdir(self.openssl) + if self.have_openssl_ech(): + for domain in [self.domain1, self.domain2]: + p = subprocess.run(args=[ + self.openssl, 'ech', + '-out', os.path.join(ca_dir, f'{domain}.echconfig'), + '-public_name', f'public.{domain}' + ], capture_output=True, text=True, check=True) + if p.returncode != 0: + raise EnvError(f"Error generating ECHConfig for {domain}") def setup(self): os.makedirs(self.gen_dir, exist_ok=True) @@ -734,6 +775,28 @@ def get_credentials(self, domain) -> Optional[Credentials]: return creds[0] return None + def get_echconfig_file(self, domain) -> Optional[str]: + ech_file = os.path.join(self.gen_dir, f"ca/{domain}.echconfig") + if os.path.exists(ech_file): + return ech_file + return None + + def get_echconfig_arg(self, domain) -> Optional[str]: + ech_file = self.get_echconfig_file(domain) + if ech_file: + ech_config = '' + in_pem = False + with open(ech_file) as fd: + for line in fd.read().splitlines(): + if not in_pem and line == '-----BEGIN ECHCONFIG-----': + in_pem = True + elif in_pem and line == '-----END ECHCONFIG-----': + break + elif in_pem: + ech_config += line + return ech_config + return None + @property def verbose(self) -> int: return self._verbose diff --git a/tests/http/testenv/nghttpx.py b/tests/http/testenv/nghttpx.py index 296b6b4679c9..87ba7ec760d3 100644 --- a/tests/http/testenv/nghttpx.py +++ b/tests/http/testenv/nghttpx.py @@ -59,6 +59,7 @@ def __init__(self, env: Env, name: str, domain: str, cred_name: str): self._cred_name = self._def_cred_name = cred_name self._loaded_cred_name = '' self._version = NghttpxUtil.version(self._cmd) + self._uses_ech = False def supports_h3(self): return NghttpxUtil.version_with_h3(self._version) @@ -83,6 +84,10 @@ def port(self): def port_is_quic(self): return self._port_is_quic + @property + def uses_ech(self): + return self._uses_ech + def exists(self): return self._cmd and os.path.exists(self._cmd) @@ -221,6 +226,10 @@ def __init__(self, env: Env): domain=env.domain1, cred_name=env.domain1) self._https_port = 0 + @property + def https_port(self): + return self._https_port + def initial_start(self): super().initial_start() @@ -269,6 +278,74 @@ def start(self, wait_live=True): '--frontend-http3-max-connection-window-size=100M', # f'--frontend-quic-debug-log', ]) + if self.env.have_nghttpx_ech(): + ech_conf = self.env.get_echconfig_file(self._cred_name) + if ech_conf: + args.extend([ + '--ech-config-file', ech_conf + ]) + self._uses_ech = True + self._error_fd = open(self._stderr, 'a') # noqa: SIM115 + self._process = subprocess.Popen(args=args, stderr=self._error_fd) + if self._process.returncode is not None: + return False + return not wait_live or self.wait_live(timeout=timedelta(seconds=Env.SERVER_TIMEOUT)) + + +class NghttpxTcp(Nghttpx): + + PORT_SPECS: ClassVar[Dict[str, int]] = { + 'nghttpx_tcp': socket.SOCK_STREAM, + } + + def __init__(self, env: Env): + super().__init__(env=env, name='nghttpx-tcp', + domain=env.domain1, cred_name=env.domain1) + + def initial_start(self): + super().initial_start() + + def startup(ports: Dict[str, int]) -> bool: + self._port = ports['nghttpx_tcp'] + if self.start(): + self.env.update_ports(ports) + return True + self.stop() + self._port = 0 + return False + + return alloc_ports_and_do(NghttpxTcp.PORT_SPECS, startup, + self.env.gen_root, max_tries=3) + + def start(self, wait_live=True): + self._mkpath(self._tmp_dir) + if self._process: + self.stop() + creds = self.env.get_credentials(self._cred_name) + assert creds # convince pytype this is not None + self._loaded_cred_name = self._cred_name + args = [self._cmd, f'--frontend=*,{self._port};tls'] + args.extend([ + f'--backend=127.0.0.1,{self.env.http_port}', + '--log-level=ERROR', + f'--pid-file={self._pid_file}', + f'--errorlog-file={self._error_log}', + f'--conf={self._conf_file}', + f'--cacert={self.env.ca.cert_file}', + creds.pkey_file, + creds.cert_file, + '--frontend-http3-window-size=1M', + '--frontend-http3-max-window-size=10M', + '--frontend-http3-connection-window-size=10M', + '--frontend-http3-max-connection-window-size=100M', + ]) + if self.env.have_nghttpx_ech(): + ech_conf = self.env.get_echconfig_file(self._cred_name) + if ech_conf: + args.extend([ + '--ech-config-file', ech_conf + ]) + self._uses_ech = True self._error_fd = open(self._stderr, 'a') # noqa: SIM115 self._process = subprocess.Popen(args=args, stderr=self._error_fd) if self._process.returncode is not None: From bea2e5d6d1dd11b443eef091681cd132209b5bc2 Mon Sep 17 00:00:00 2001 From: ns-ylambert Date: Wed, 30 Sep 2026 19:52:28 +0200 Subject: [PATCH 433/481] rustls: consume CURLOPT_SSLCERT_BLOB / CURLOPT_SSLKEY_BLOB in the rustls backend `curl/lib/vtls/rustls.c` reads only the file-path client-cert fields (`conn_config->clientcert`, `conn_config->key`) via `read_file_into()`. The blob fields populated by `CURLOPT_SSLCERT_BLOB` / `CURLOPT_SSLKEY_BLOB` are never consulted, so callers that hand the client identity as in-memory blobs have their cert and key silently dropped and the server sees an anonymous TLS connection. No new option is introduced; existing `CURLOPT_SSLCERT_BLOB` / `CURLOPT_SSLKEY_BLOB` just start working on curl+rustls-ffi. Closes #23196 --- .../opts/CURLOPT_PROXY_SSLCERT_BLOB.md | 5 +- .../libcurl/opts/CURLOPT_PROXY_SSLKEY_BLOB.md | 3 +- docs/libcurl/opts/CURLOPT_SSLCERT_BLOB.md | 4 +- docs/libcurl/opts/CURLOPT_SSLKEY_BLOB.md | 5 +- lib/vtls/rustls.c | 50 ++++-- tests/data/Makefile.am | 1 + tests/data/test2095 | 57 +++++++ tests/libtest/Makefile.inc | 1 + tests/libtest/lib2095.c | 148 ++++++++++++++++++ 9 files changed, 259 insertions(+), 15 deletions(-) create mode 100644 tests/data/test2095 create mode 100644 tests/libtest/lib2095.c diff --git a/docs/libcurl/opts/CURLOPT_PROXY_SSLCERT_BLOB.md b/docs/libcurl/opts/CURLOPT_PROXY_SSLCERT_BLOB.md index 5be7b134a0c0..da4ef48f6458 100644 --- a/docs/libcurl/opts/CURLOPT_PROXY_SSLCERT_BLOB.md +++ b/docs/libcurl/opts/CURLOPT_PROXY_SSLCERT_BLOB.md @@ -13,6 +13,7 @@ Protocol: - TLS TLS-backend: - OpenSSL + - Rustls - Schannel Added-in: 7.71.0 --- @@ -35,8 +36,8 @@ CURLcode curl_easy_setopt(CURL *handle, CURLOPT_PROXY_SSLCERT_BLOB, Pass a pointer to a curl_blob structure, which contains information (pointer and size) about a memory block with binary data of the certificate used to connect to the HTTPS proxy. The format must be "P12" on Schannel. The format -must be "P12" or "PEM" on OpenSSL. The string "P12" or "PEM" must be specified -with CURLOPT_PROXY_SSLCERTTYPE(3). +must be "P12" or "PEM" on OpenSSL. The format must be "PEM" on Rustls. The +string "P12" or "PEM" must be specified with CURLOPT_PROXY_SSLCERTTYPE(3). If the blob is initialized with the flags member of struct curl_blob set to CURL_BLOB_COPY, the application does not have to keep the buffer around after diff --git a/docs/libcurl/opts/CURLOPT_PROXY_SSLKEY_BLOB.md b/docs/libcurl/opts/CURLOPT_PROXY_SSLKEY_BLOB.md index 65f940b8610c..230adedee6e4 100644 --- a/docs/libcurl/opts/CURLOPT_PROXY_SSLKEY_BLOB.md +++ b/docs/libcurl/opts/CURLOPT_PROXY_SSLKEY_BLOB.md @@ -12,6 +12,7 @@ Protocol: - TLS TLS-backend: - OpenSSL + - Rustls Added-in: 7.71.0 --- @@ -32,7 +33,7 @@ CURLcode curl_easy_setopt(CURL *handle, CURLOPT_PROXY_SSLKEY_BLOB, Pass a pointer to a curl_blob structure that contains information (pointer and size) about the private key for connecting to the HTTPS proxy. Compatible with -OpenSSL. The format (like "PEM") must be specified with +OpenSSL and Rustls. The format (like "PEM") must be specified with CURLOPT_PROXY_SSLKEYTYPE(3). If the blob is initialized with the flags member of struct curl_blob set to diff --git a/docs/libcurl/opts/CURLOPT_SSLCERT_BLOB.md b/docs/libcurl/opts/CURLOPT_SSLCERT_BLOB.md index aa69fbbc896a..559058975bc8 100644 --- a/docs/libcurl/opts/CURLOPT_SSLCERT_BLOB.md +++ b/docs/libcurl/opts/CURLOPT_SSLCERT_BLOB.md @@ -12,6 +12,7 @@ Protocol: - TLS TLS-backend: - OpenSSL + - Rustls - Schannel - mbedTLS - wolfSSL @@ -36,7 +37,8 @@ CURLcode curl_easy_setopt(CURL *handle, CURLOPT_SSLCERT_BLOB, Pass a pointer to a curl_blob structure, which contains (pointer and size) a client certificate. The format must be "P12" on Schannel. The format must be "P12" or "PEM" on OpenSSL. The format must be "DER" or "PEM" on mbedTLS. The -format must be specified with CURLOPT_SSLCERTTYPE(3). +format must be "PEM" on Rustls. The format must be specified with +CURLOPT_SSLCERTTYPE(3). If the blob is initialized with the flags member of struct curl_blob set to CURL_BLOB_COPY, the application does not have to keep the buffer around after diff --git a/docs/libcurl/opts/CURLOPT_SSLKEY_BLOB.md b/docs/libcurl/opts/CURLOPT_SSLKEY_BLOB.md index a062d39deba8..bf947a9ac10f 100644 --- a/docs/libcurl/opts/CURLOPT_SSLKEY_BLOB.md +++ b/docs/libcurl/opts/CURLOPT_SSLKEY_BLOB.md @@ -11,6 +11,7 @@ Protocol: - TLS TLS-backend: - OpenSSL + - Rustls - wolfSSL Added-in: 7.71.0 --- @@ -31,8 +32,8 @@ CURLcode curl_easy_setopt(CURL *handle, CURLOPT_SSLKEY_BLOB, # DESCRIPTION Pass a pointer to a curl_blob structure, which contains information (pointer -and size) for a private key. Compatible with OpenSSL. The format (like "PEM") -must be specified with CURLOPT_SSLKEYTYPE(3). +and size) for a private key. Compatible with OpenSSL and Rustls. The format +(like "PEM") must be specified with CURLOPT_SSLKEYTYPE(3). If the blob is initialized with the flags member of struct curl_blob set to CURL_BLOB_COPY, the application does not have to keep the buffer around after diff --git a/lib/vtls/rustls.c b/lib/vtls/rustls.c index bdcfa761b9d7..72edac9e730d 100644 --- a/lib/vtls/rustls.c +++ b/lib/vtls/rustls.c @@ -855,29 +855,60 @@ init_config_builder_client_auth(struct Curl_easy *data, rustls_result rr; const struct rustls_certified_key *certified_key = NULL; CURLcode result = CURLE_OK; + const bool have_cert = conn_config->clientcert || conn_config->cert_blob; + const bool have_key = conn_config->key || conn_config->key_blob; - if(conn_config->clientcert && !conn_config->key) { - failf(data, "rustls: must provide key with certificate '%s'", - conn_config->clientcert); + if(have_cert && !have_key) { + failf(data, "rustls: must provide key with certificate"); return CURLE_SSL_CERTPROBLEM; } - else if(!conn_config->clientcert && conn_config->key) { - failf(data, "rustls: must provide certificate with key '%s'", - conn_config->key); + else if(!have_cert && have_key) { + failf(data, "rustls: must provide certificate with key"); + return CURLE_SSL_CERTPROBLEM; + } + + if(conn_config->cert_type && !curl_strequal(conn_config->cert_type, "PEM")) { + failf(data, "rustls: unsupported certificate type '%s', only 'PEM' is " + "supported", conn_config->cert_type); + return CURLE_SSL_CERTPROBLEM; + } + if(conn_config->key_type && !curl_strequal(conn_config->key_type, "PEM")) { + failf(data, "rustls: unsupported key type '%s', only 'PEM' is supported", + conn_config->key_type); return CURLE_SSL_CERTPROBLEM; } curlx_dyn_init(&cert_contents, DYN_CERTFILE_SIZE); curlx_dyn_init(&key_contents, DYN_KEYFILE_SIZE); - if(!read_file_into(conn_config->clientcert, &cert_contents)) { + if(conn_config->cert_blob) { + result = curlx_dyn_addn(&cert_contents, + conn_config->cert_blob->data, + conn_config->cert_blob->len); + if(result) { + failf(data, "rustls: failed to copy client certificate blob (%s)", + result == CURLE_TOO_LARGE ? "too large" : "out of memory"); + goto cleanup; + } + } + else if(!read_file_into(conn_config->clientcert, &cert_contents)) { failf(data, "rustls: failed to read client certificate file: '%s'", conn_config->clientcert); result = CURLE_SSL_CERTPROBLEM; goto cleanup; } - if(!read_file_into(conn_config->key, &key_contents)) { + if(conn_config->key_blob) { + result = curlx_dyn_addn(&key_contents, + conn_config->key_blob->data, + conn_config->key_blob->len); + if(result) { + failf(data, "rustls: failed to copy key blob (%s)", + result == CURLE_TOO_LARGE ? "too large" : "out of memory"); + goto cleanup; + } + } + else if(!read_file_into(conn_config->key, &key_contents)) { failf(data, "rustls: failed to read key file: '%s'", conn_config->key); result = CURLE_SSL_CERTPROBLEM; @@ -1070,7 +1101,8 @@ static CURLcode cr_init_backend(struct Curl_cfilter *cf, } } - if(conn_config->clientcert || conn_config->key) { + if(conn_config->clientcert || conn_config->cert_blob || + conn_config->key || conn_config->key_blob) { result = init_config_builder_client_auth(data, conn_config, config_builder); diff --git a/tests/data/Makefile.am b/tests/data/Makefile.am index 628614dc75e6..7f68e9d0d404 100644 --- a/tests/data/Makefile.am +++ b/tests/data/Makefile.am @@ -1914,6 +1914,7 @@ TESTCASES = \ test2092 \ test2093 \ test2094 \ + test2095 \ test2100 \ test2101 \ test2102 \ diff --git a/tests/data/test2095 b/tests/data/test2095 new file mode 100644 index 000000000000..06acabbb0e5f --- /dev/null +++ b/tests/data/test2095 @@ -0,0 +1,57 @@ + + + + +HTTPS +HTTP GET +Client Auth +libtest + + + +# Server-side + + +HTTP/1.1 200 OK +Date: Tue, 09 Nov 2010 14:49:00 GMT +Server: test-server/fake +Content-Length: 7 + +MooMoo + + + +# Client-side + + +SSL +!Schannel +!GnuTLS +!mbedtls +local-http + + +https-mtls + + +HTTPS with in-memory client auth using blob options + + +https://localhost:%HTTPS-MTLSPORT/%TESTNUMBER %CERTDIR/certs/test-ca.crt %CERTDIR/certs/test-client-cert.crt %CERTDIR/certs/test-client-cert.key + + +%LIBTESTS lib%TESTNUMBER check + + + +# Verify data after the test has been "shot" + + +GET /%TESTNUMBER HTTP/1.1 +Host: localhost:%HTTPS-MTLSPORT +User-Agent: CURLOPT_SSLCERT_BLOB +Accept: */* + + + + diff --git a/tests/libtest/Makefile.inc b/tests/libtest/Makefile.inc index 0395dbb3a296..0f7e749e7490 100644 --- a/tests/libtest/Makefile.inc +++ b/tests/libtest/Makefile.inc @@ -302,6 +302,7 @@ TESTS_C = \ lib2023.c \ lib2032.c \ lib2082.c \ + lib2095.c \ lib2118.c \ lib2270.c \ lib2301.c \ diff --git a/tests/libtest/lib2095.c b/tests/libtest/lib2095.c new file mode 100644 index 000000000000..e1575112c9d8 --- /dev/null +++ b/tests/libtest/lib2095.c @@ -0,0 +1,148 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +/* CURLOPT_SSLCERT_BLOB / CURLOPT_SSLKEY_BLOB -- in-memory client auth */ + +#include "first.h" + +static int t2095_loadfile(const char *filename, + void **filedata, + size_t *filesize) +{ + size_t datasize = 0; + void *data = NULL; + if(filename) { + FILE *fIn = curlx_fopen(filename, "rb"); + + if(fIn) { + long tell = 0; + bool ok = fseek(fIn, 0, SEEK_END) == 0; + if(ok) + tell = ftell(fIn); + if(tell < 0) + ok = FALSE; + else + datasize = (size_t)tell; + if(ok) + ok = fseek(fIn, 0, SEEK_SET) == 0; + if(ok) + data = curlx_malloc(datasize + 1); + if(!data || ((int)fread(data, datasize, 1, fIn) != 1)) + ok = FALSE; + curlx_fclose(fIn); + if(!ok) { + curlx_safefree(data); + datasize = 0; + } + } + } + *filesize = datasize; + *filedata = data; + return data ? 1 : 0; +} + +static CURLcode t2095_cert_blob(const char *url, + const char *cafile, + const char *certfile, + const char *keyfile) +{ + CURLcode result = CURLE_OUT_OF_MEMORY; + CURL *curl; + struct curl_blob blob; + size_t sz; + void *data; + + curl = curl_easy_init(); + if(!curl) { + curl_mfprintf(stderr, "curl_easy_init() failed\n"); + return CURLE_FAILED_INIT; + } + + curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L); + curl_easy_setopt(curl, CURLOPT_HEADER, 1L); + curl_easy_setopt(curl, CURLOPT_URL, url); + curl_easy_setopt(curl, CURLOPT_USERAGENT, "CURLOPT_SSLCERT_BLOB"); + + if(t2095_loadfile(cafile, &data, &sz)) { + blob.data = data; + blob.len = sz; + blob.flags = CURL_BLOB_COPY; + curl_easy_setopt(curl, CURLOPT_CAINFO_BLOB, &blob); + curlx_free(data); + } + + result = CURLE_OUT_OF_MEMORY; + if(t2095_loadfile(certfile, &data, &sz)) { + blob.data = data; + blob.len = sz; + blob.flags = CURL_BLOB_COPY; + result = curl_easy_setopt(curl, CURLOPT_SSLCERT_BLOB, &blob); + curlx_free(data); + } + if(result) + goto cleanup; + + if(t2095_loadfile(keyfile, &data, &sz)) { + blob.data = data; + blob.len = sz; + blob.flags = CURL_BLOB_COPY; + result = curl_easy_setopt(curl, CURLOPT_SSLKEY_BLOB, &blob); + curlx_free(data); + } + if(result) + goto cleanup; + + result = curl_easy_perform(curl); + +cleanup: + curl_easy_cleanup(curl); + return result; +} + +static CURLcode test_lib2095(const char *URL) +{ + CURLcode result = CURLE_OK; + curl_global_init(CURL_GLOBAL_ALL); + if(!strcmp("check", URL)) { + CURLcode w = CURLE_OK; + struct curl_blob blob = { CURL_UNCONST("x"), 1, 0 }; + CURL *curl = curl_easy_init(); + if(curl) { + w = curl_easy_setopt(curl, CURLOPT_SSLCERT_BLOB, &blob); + if(w) + curl_mprintf("CURLOPT_SSLCERT_BLOB is not supported\n"); + else { + w = curl_easy_setopt(curl, CURLOPT_SSLKEY_BLOB, &blob); + if(w) + curl_mprintf("CURLOPT_SSLKEY_BLOB is not supported\n"); + } + curl_easy_cleanup(curl); + } + result = w; + } + else + result = t2095_cert_blob(URL, libtest_arg2, libtest_arg3, libtest_arg4); + curl_global_cleanup(); + return result; +} From 39b06935be0638e6e320c3e596bc8759fe749775 Mon Sep 17 00:00:00 2001 From: Stefan Eissing Date: Wed, 7 Oct 2026 10:55:36 +0200 Subject: [PATCH 434/481] websocket: auto PONG with client reader fix When getting websocket upload data via a client reader, the download code path MUST NOT send PONG frames directly to the connection. Instead those control frames need to be picked up by the client reader. Add a flag in the webocket context when a client reader is setup and refrain from sending in download writes/receives. Reported-by: Anthropic Closes #23296 --- lib/ws.c | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/lib/ws.c b/lib/ws.c index 26023245909a..704f7ee27681 100644 --- a/lib/ws.c +++ b/lib/ws.c @@ -123,6 +123,7 @@ struct websocket { struct curl_ws_frame recvframe; /* the current WS FRAME received */ struct ws_cntrl_frame pending; /* a control frame pending to be sent */ size_t sendbuf_payload; /* number of payload bytes in sendbuf */ + BIT(using_reader); /* sending WS data is handled via a client reader */ }; #ifdef CURLVERBOSE @@ -617,13 +618,6 @@ struct ws_cw_dec_ctx { static CURLcode ws_flush(struct Curl_easy *data, struct websocket *ws, bool blocking); -static CURLcode ws_enc_send(struct Curl_easy *data, - struct websocket *ws, - const uint8_t *buffer, - size_t buflen, - curl_off_t fragsize, - unsigned int flags, - size_t *pnsent); static CURLcode ws_enc_add_pending(struct Curl_easy *data, struct websocket *ws); @@ -760,7 +754,7 @@ static CURLcode ws_cw_write(struct Curl_easy *data, } out: - if(!result) { + if(!result && !ws->using_reader) { result = ws_flush(data, ws, Curl_api_is_in_callback(data)); if(result == CURLE_AGAIN) result = CURLE_OK; @@ -1284,13 +1278,22 @@ static CURLcode cr_ws_read(struct Curl_easy *data, return result; } +static curl_off_t cr_ws_total_length(struct Curl_easy *data, + struct Curl_creader *reader) +{ + /* this reader changes length depending on input */ + (void)data; + (void)reader; + return -1; +} + static const struct Curl_crtype ws_cr_encode = { "ws-encode", cr_ws_init, cr_ws_read, cr_ws_close, Curl_creader_def_needs_rewind, - Curl_creader_def_total_length, + cr_ws_total_length, Curl_creader_def_resume_from, Curl_creader_def_cntrl, Curl_creader_def_is_paused, @@ -1480,6 +1483,7 @@ CURLcode Curl_ws_accept(struct Curl_easy *data, if(result) goto out; ws_enc_reader = NULL; /* owned by transfer now */ + ws->using_reader = TRUE; } /* start over with sending */ @@ -1687,7 +1691,7 @@ CURLcode curl_ws_recv(CURL *curl, void *buffer, ws->recvframe.bytesleft); /* all's well, try to send any pending control. we do not know * when the application will call `curl_ws_send()` again. */ - if(!data->set.ws_raw_mode && ws->pending.type) { + if(!data->set.ws_raw_mode && !ws->using_reader && ws->pending.type) { CURLcode r2 = ws_enc_add_pending(data, ws); if(!r2) (void)ws_flush(data, ws, Curl_api_is_in_callback(data)); From 582cdc8cf2c9cdaf484a2076a714557351a91461 Mon Sep 17 00:00:00 2001 From: Stefan Eissing Date: Thu, 8 Oct 2026 10:53:01 +0200 Subject: [PATCH 435/481] pytest: start all servers early Move server fixtures for dnsd and danted into conftest.py, so they start early and port allocations have less chance to be confused. Starting them later while many other tests are running in parallel has the chance that an allocated port is picked up by a client before the server can re-grab it. Closes #23310 --- tests/http/conftest.py | 31 ++++++++++++++++++++++++++++++- tests/http/test_21_resolve.py | 11 +---------- tests/http/test_40_socks.py | 11 ----------- tests/http/testenv/dante.py | 2 +- 4 files changed, 32 insertions(+), 23 deletions(-) diff --git a/tests/http/conftest.py b/tests/http/conftest.py index 2805c650d6b3..54af4e855b3c 100644 --- a/tests/http/conftest.py +++ b/tests/http/conftest.py @@ -33,7 +33,17 @@ sys.path.append(os.path.join(os.path.dirname(__file__), ".")) -from testenv import Env, Httpd, Nghttpx, NghttpxFwd, NghttpxQuic, NghttpxTcp, Sshd +from testenv import ( + Dante, + Dnsd, + Env, + Httpd, + Nghttpx, + NghttpxFwd, + NghttpxQuic, + NghttpxTcp, + Sshd, +) from testenv.h2o import H2oProxy, H2oServer log = logging.getLogger(__name__) @@ -94,6 +104,25 @@ def env(pytestconfig, env_config) -> Env: return env +@pytest.fixture(scope='session') +def danted(env) -> Generator[Union[Dante, bool], None, None]: + if Env.has_danted(): + danted = Dante(env=env) + assert danted.initial_start() + yield danted + danted.stop() + else: + yield False + + +@pytest.fixture(scope='session') +def dnsd(env) -> Generator[Dnsd, None, None]: + dnsd = Dnsd(env=env) + assert dnsd.initial_start() + yield dnsd + dnsd.stop() + + @pytest.fixture(scope="session") def httpd(env) -> Generator[Httpd, None, None]: httpd = Httpd(env=env) diff --git a/tests/http/test_21_resolve.py b/tests/http/test_21_resolve.py index d3486ea82f35..9e9ea4bfd185 100644 --- a/tests/http/test_21_resolve.py +++ b/tests/http/test_21_resolve.py @@ -26,10 +26,9 @@ import os import re from datetime import timedelta -from typing import Generator import pytest -from testenv import CurlClient, Dnsd, Env, LocalClient +from testenv import CurlClient, Env, LocalClient log = logging.getLogger(__name__) @@ -38,14 +37,6 @@ @pytest.mark.skipif(condition=not Env.curl_has_feature('AsynchDNS'), reason="needs AsynchDNS") class TestResolve: - @pytest.fixture(scope='class') - @classmethod - def dnsd(cls, env: Env) -> Generator[Dnsd, None, None]: - dnsd = Dnsd(env=env) - assert dnsd.initial_start() - yield dnsd - dnsd.stop() - @pytest.fixture(autouse=True, scope='class') @classmethod def _class_scope(cls, env, httpd): diff --git a/tests/http/test_40_socks.py b/tests/http/test_40_socks.py index e29d574c1ae5..d29e03314367 100644 --- a/tests/http/test_40_socks.py +++ b/tests/http/test_40_socks.py @@ -24,8 +24,6 @@ # import logging import os -import time -from typing import Generator import pytest from testenv import CurlClient, Dante, Env @@ -36,15 +34,6 @@ @pytest.mark.skipif(condition=not Env.has_danted(), reason="missing danted") class TestSocks: - @pytest.fixture(scope='class') - @classmethod - def danted(cls, env: Env) -> Generator[Dante, None, None]: - danted = Dante(env=env) - assert danted.initial_start() - time.sleep(1) - yield danted - danted.stop() - @pytest.fixture(autouse=True, scope='class') @classmethod def _class_scope(cls, env, httpd): diff --git a/tests/http/testenv/dante.py b/tests/http/testenv/dante.py index f42e3f875534..50f7027e0880 100644 --- a/tests/http/testenv/dante.py +++ b/tests/http/testenv/dante.py @@ -139,10 +139,10 @@ def wait_live(self, timeout: timedelta): ]) try_until = datetime.now(timezone.utc) + timeout while datetime.now(timezone.utc) < try_until: + time.sleep(.1) r = curl.http_get(url=f'http://{self.env.domain1}:{self.env.http_port}/') if r.exit_code == 0: return True - time.sleep(.1) log.error(f"Server still not responding after {timeout}") return False From b0a8bdbec5f03585d6cb536dbc4af887a77f6633 Mon Sep 17 00:00:00 2001 From: Viktor Szakats Date: Thu, 8 Oct 2026 13:42:50 +0200 Subject: [PATCH 436/481] ldap: tidy up variable scope, merge nested `#if`s Closes #23314 --- lib/ldap.c | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/lib/ldap.c b/lib/ldap.c index 70422f217b47..10f9d6ffb1ac 100644 --- a/lib/ldap.c +++ b/lib/ldap.c @@ -419,10 +419,9 @@ static CURLcode ldap_do(struct Curl_easy *data, bool *done) ldap_set_option(server, LDAP_OPT_SERVER_CERTIFICATE, (void *)(uintptr_t)bypass_cert_verify); } -#else /* !USE_WIN32_LDAP */ +#elif defined(LDAP_OPT_X_TLS) /* !USE_WIN32_LDAP */ int ldap_option; const char *ldap_ca = conn->ssl_config.CAfile; -#ifdef LDAP_OPT_X_TLS if(conn->ssl_config.verifypeer) { /* OpenLDAP SDK supports BASE64 files. */ if(conn->ssl_config.cert_type && @@ -464,17 +463,14 @@ static CURLcode ldap_do(struct Curl_easy *data, bool *done) result = CURLE_SSL_CERTPROBLEM; goto quit; } -#else /* !LDAP_OPT_X_TLS */ - (void)ldap_option; - (void)ldap_ca; +#else /* !USE_WIN32_LDAP && !LDAP_OPT_X_TLS */ /* we should probably never come up to here since configure should check in first place if we can support LDAP SSL/TLS */ failf(data, "LDAP local: SSL/TLS not supported with this version " "of the OpenLDAP toolkit"); result = CURLE_SSL_CERTPROBLEM; goto quit; -#endif /* LDAP_OPT_X_TLS */ -#endif /* USE_WIN32_LDAP */ +#endif #endif /* HAVE_LDAP_SSL */ } else if(data->set.use_ssl > CURLUSESSL_TRY) { From e8adc72e8332e8274292ebf7faae611e237544c1 Mon Sep 17 00:00:00 2001 From: Viktor Szakats Date: Thu, 8 Oct 2026 05:04:22 +0200 Subject: [PATCH 437/481] schannel: fix guard for old MSVC workaround Syncing with its sibling workaround in lib/ldap.c. Fixing (seen in 'CM VS2012, Debug, x64, Schannel, Shared, Build-tests' temporary job): ``` lib/vtls/schannel.c(551): error C2220: warning treated as error - no 'object' file generated lib/vtls/schannel.c(551): warning C4306: 'type cast' : conversion from 'int' to 'LPCSTR' of greater size lib/vtls/schannel_verify.c(680): warning C4306: 'type cast' : conversion from 'int' to 'LPCSTR' of greater size ``` Ref: https://ci.appveyor.com/project/curlorg/curl/builds/54856856/job/v166htpgc6v2fs8u#L197 Affects 64-bit builds only. (not tested in CI with VS2012) Follow-up to 2e1a045d8985e5daa4d9a4f908ed870a16d8e41e #17931 Cherry-picked from #23306 Closes #23315 --- lib/vtls/schannel_int.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/vtls/schannel_int.h b/lib/vtls/schannel_int.h index ccbb63d7e20c..fdf433fcff7c 100644 --- a/lib/vtls/schannel_int.h +++ b/lib/vtls/schannel_int.h @@ -31,7 +31,7 @@ #include "vtls/vtls.h" #include "curl_sha256.h" -#if defined(_MSC_VER) && (_MSC_VER < 1700) +#if defined(_MSC_VER) && (_MSC_VER <= 1700) /* Workaround for warning: 'type cast' : conversion from 'int' to 'LPCSTR' of greater size */ #undef CERT_STORE_PROV_MEMORY From 2874cc647b0b46686c1b5b36bb927a5cb70d13fc Mon Sep 17 00:00:00 2001 From: Viktor Szakats Date: Wed, 7 Oct 2026 22:45:02 +0200 Subject: [PATCH 438/481] mqttd: work around bogus VS2019 32-bit compiler warning Seen in temporary job 'CM VS2019, Release, x86, OpenSSL 3.0 + Schannel, Shared, !verbose, Build-tests', with Visual Studio Community 2019 v16.11.50: ``` -- Building for: Visual Studio 16 2019 -- Using CMake version 4.1.0 -- Selecting Windows SDK version 10.0.26100.0 to target Windows 10.0.17763. -- The C compiler identification is MSVC 19.29.30159.0 -- Check for working C compiler: C:/Program Files (x86)/Microsoft Visual Studio/2019/Community/VC/Tools/MSVC/14.29.30133/bin/Hostx64/x86/cl.exe - skipped -- CMake platform flags: WIN32 MSVC-1929 [...] -- Found _WIN32_WINNT=0x0a00 -- Picky compiler options: -W4 -Wall -wd4061 -wd4191 -wd4255 -wd4464 -wd4548 -wd4574 -wd4668 -wd4710 -wd4711 -wd4746 -wd4820 -wd5045 [...] tests\server\mqttd.c(169,19): error C2220: the following warning is treated as an error tests\server\mqttd.c(169,19): warning C4777: 'fprintf' : format string '%x' requires an argument of type 'unsigned int', but variadic argument 3 has type 'size_t' tests\server\mqttd.c(169,19): message : the sizes of types 'size_t' and 'unsigned int' might differ on other platforms tests\server\mqttd.c(169,19): message : consider using '%zx' in the format string ``` Ref: https://ci.appveyor.com/project/curlorg/curl/builds/54855420/job/71ccddu696xhn95q#L263 (The suggested `%zx` solution does not fit other compilers.) Cherry-picked from #23306 Closes #23317 --- tests/server/mqttd.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/server/mqttd.c b/tests/server/mqttd.c index 74bebb8efb2a..d0f9ea1603e8 100644 --- a/tests/server/mqttd.c +++ b/tests/server/mqttd.c @@ -166,9 +166,9 @@ static void logprotocol(mqttdir dir, optr += 2; left -= 2; } - fprintf(output, "%s %s %x %s\n", + fprintf(output, "%s %s %lx %s\n", dir == FROM_CLIENT ? "client" : "server", - prefix, (unsigned int)remlen, data); + prefix, (unsigned long)remlen, data); } /* return 0 on success */ From 424aa994ac223c0f90839aadee0fb59595344fce Mon Sep 17 00:00:00 2001 From: Daniel Stenberg Date: Thu, 8 Oct 2026 23:08:12 +0200 Subject: [PATCH 439/481] config2setopts: restore error on --proxy for proxy disabled builds Follow-up to d8363b77204396bd2431 Reported-by: Dan Fandrich Closes #23320 --- src/config2setopts.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/config2setopts.c b/src/config2setopts.c index ed620e4fdc8e..736cdd3db1a2 100644 --- a/src/config2setopts.c +++ b/src/config2setopts.c @@ -1237,7 +1237,7 @@ CURLcode config2setopts(struct OperationConfig *config, gen_cb_setopts(config, per, curl); result = proxy_setopts(config, curl); - if(setopt_bad(result)) + if(setopt_bad(result) || config->synthetic_error) return result; result = credentials_and_headers_setopts(config, curl); From 5e201366a0e06f8d8fdd1027d00ab8ab1103404f Mon Sep 17 00:00:00 2001 From: Joshua Rogers Date: Thu, 8 Oct 2026 22:40:39 -0700 Subject: [PATCH 440/481] imap: close the connection on premature done instead of finalizing A premature done() no longer sends the APPEND terminator; it marks the connection for closure like the error path does. Ref: #23260 Closes #23322 --- lib/imap.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/lib/imap.c b/lib/imap.c index 060715ed603e..036bf91951d8 100644 --- a/lib/imap.c +++ b/lib/imap.c @@ -2002,15 +2002,13 @@ static CURLcode imap_done(struct Curl_easy *data, CURLcode status, struct imap_conn *imapc = Curl_conn_meta_get(conn, CURL_META_IMAP_CONN); struct IMAP *imap = Curl_meta_get(data, CURL_META_IMAP_EASY); - (void)premature; - if(!imapc) return CURLE_FAILED_INIT; if(!imap) return CURLE_OK; - if(status) { - CURL_TRC_M(data, "IMAP done with bad status"); + if(status || premature) { + CURL_TRC_M(data, "IMAP done, not finalizing transfer"); connclose(conn); /* marked for closure */ result = status; /* use the already set error code */ } From 1c19d96e2b04e0b83c402715dac9c9bca2e007e2 Mon Sep 17 00:00:00 2001 From: Joshua Rogers Date: Thu, 8 Oct 2026 22:40:54 -0700 Subject: [PATCH 441/481] pingpong: guard sendleft against a send reporting more than requested Use < instead of != so sendleft is not computed from a byte count that exceeds the buffer length. Ref: #23260 Closes #23323 --- lib/pingpong.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/pingpong.c b/lib/pingpong.c index 6a683c12f796..f59e31add019 100644 --- a/lib/pingpong.c +++ b/lib/pingpong.c @@ -185,7 +185,7 @@ CURLcode Curl_pp_vsendf(struct Curl_easy *data, Curl_debug(data, CURLINFO_HEADER_OUT, s, bytes_written); - if(bytes_written != write_len) { + if(bytes_written < write_len) { /* the whole chunk was not sent, keep it around and adjust sizes */ pp->sendthis = s; pp->sendsize = write_len; @@ -389,7 +389,7 @@ CURLcode Curl_pp_flushsend(struct Curl_easy *data, if(result) return result; - if(written != pp->sendleft) { + if(written < pp->sendleft) { /* only a fraction was sent */ pp->sendleft -= written; } From 354834bf905f58f7727df60d30eef7cac52f7df8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C4=B0lyas=20Serdar=20Dursun?= Date: Thu, 8 Oct 2026 22:24:23 +0300 Subject: [PATCH 442/481] connect: keep IP info and connect time when connect fails When the connect timeout triggers during the TLS handshake, the transfer reports no remote_ip, local_ip or time_connect even though TCP was connected. The same happens when the TLS handshake fails. The IP info is only stored once the whole filter chain is connected, and since 23386872d1 the connect timeout is handled in multi.c before the connect code gets to report anything. Let the filters that did connect report their info and stats when the chain fails. Test 5029 and 5030 verify. Follow-up to ea6f5c9f0fff4f069b3060f0c21f8c8ebc69b386 #14699 Follow-up to 23386872d13d518dbe8df21cc022f15120f96cdb #15119 Reported-by: Adrien Rey-Jarthon Fixes #23318 Closes #23319 --- lib/cf-https-connect.c | 1 + lib/connect.c | 14 ++++++++++++- lib/connect.h | 7 +++++++ lib/multi.c | 6 +++++- tests/data/Makefile.am | 4 +++- tests/data/test5029 | 47 ++++++++++++++++++++++++++++++++++++++++++ tests/data/test5030 | 47 ++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 123 insertions(+), 3 deletions(-) create mode 100644 tests/data/test5029 create mode 100644 tests/data/test5030 diff --git a/lib/cf-https-connect.c b/lib/cf-https-connect.c index 56993a9d1a81..fbef12390640 100644 --- a/lib/cf-https-connect.c +++ b/lib/cf-https-connect.c @@ -695,6 +695,7 @@ static CURLcode cf_hc_cntrl(struct Curl_cfilter *cf, if(!cf->connected) { switch(event) { case CF_CTRL_REPORT_STATS: + case CF_CTRL_CONN_INFO_UPDATE: for(i = 0; i < ctx->baller_count; i++) { /* Make the first baller that connected at network level report */ if(Curl_conn_cf_is_ip_connected(ctx->ballers[i].cf, data)) { diff --git a/lib/connect.c b/lib/connect.c index 603dfc73596a..c2816b4882f7 100644 --- a/lib/connect.c +++ b/lib/connect.c @@ -240,6 +240,18 @@ static void conn_report_stats(struct Curl_easy *data, int sockindex) } } +void Curl_conn_report_failed(struct Curl_easy *data, int8_t sockindex) +{ + if(!data->conn || (sockindex != FIRSTSOCKET)) + return; + /* Only filters connected at IP level have info to update. Filters + * still racing for a connection, like happy eyeballing, pass it on + * to a connected one only. */ + (void)Curl_conn_cf_cntrl(data->conn->cfilter[sockindex], data, TRUE, + CF_CTRL_CONN_INFO_UPDATE, 0, NULL); + conn_report_stats(data, sockindex); +} + CURLcode Curl_conn_connect(struct Curl_easy *data, int8_t sockindex, bool blocking, @@ -311,7 +323,7 @@ CURLcode Curl_conn_connect(struct Curl_easy *data, CURL_TRC_CF(data, cf, "Curl_conn_connect(), filter returned %d", (int)result); VERBOSE(Curl_conn_trc_filters(data, sockindex, "failed to connect")); - conn_report_stats(data, sockindex); + Curl_conn_report_failed(data, sockindex); goto out; } diff --git a/lib/connect.h b/lib/connect.h index 54976fd62ed7..a05c1b8ea0b0 100644 --- a/lib/connect.h +++ b/lib/connect.h @@ -89,6 +89,13 @@ CURLcode Curl_conn_setup(struct Curl_easy *data, CURLcode Curl_conn_connect(struct Curl_easy *data, int8_t sockindex, bool blocking, bool *done); +/** + * The filter chain at `sockindex` failed to connect, e.g. the TLS handshake + * timed out. Let the filters that did connect, like the TCP socket, report + * their IP information and stats to the transfer. + */ +void Curl_conn_report_failed(struct Curl_easy *data, int8_t sockindex); + /* Set conn to allow multiplexing. */ void Curl_conn_set_multiplex(struct connectdata *conn); diff --git a/lib/multi.c b/lib/multi.c index a79a8a9d267f..e7e7811adf1e 100644 --- a/lib/multi.c +++ b/lib/multi.c @@ -1821,10 +1821,14 @@ static bool multi_handle_timeout(struct Curl_easy *data, timerid base_timer = Curl_is_connecting(data) ? TIMER_STARTSINGLE : TIMER_STARTOP; timediff_t elapsed_ms = Curl_pgrs_since_ms(data, NULL, base_timer); - if(data->mstate == MSTATE_CONNECTING) + if(data->mstate == MSTATE_CONNECTING) { failf(data, "%s timed out after %" FMT_TIMEDIFF_T " milliseconds", data->conn->bits.dns_resolved ? "Connection" : "Resolving", elapsed_ms); + /* keep what we know about a partly done connect, e.g. TCP done + * but TLS handshake still ongoing */ + Curl_conn_report_failed(data, FIRSTSOCKET); + } else { struct SingleRequest *k = &data->req; if(k->size != -1) { diff --git a/tests/data/Makefile.am b/tests/data/Makefile.am index 7f68e9d0d404..e02c1330acae 100644 --- a/tests/data/Makefile.am +++ b/tests/data/Makefile.am @@ -2174,7 +2174,9 @@ TESTCASES = \ test5025 \ test5026 \ test5027 \ - test5028 + test5028 \ + test5029 \ + test5030 EXTRA_DIST = \ $(TESTCASES) \ diff --git a/tests/data/test5029 b/tests/data/test5029 new file mode 100644 index 000000000000..f0a15af24071 --- /dev/null +++ b/tests/data/test5029 @@ -0,0 +1,47 @@ + + + + +HTTPS +connect timeout + + + +# Server-side + + + +# Client-side + + +SSL + + +http + + +-w IP info and connect time after TLS handshake timeout + +# The plain HTTP server never answers the TLS ClientHello, so the TCP +# connect succeeds and the TLS handshake times out + +-k https://%HOSTIP:%HTTPPORT/%TESTNUMBER --connect-timeout 2 -w 'remote_ip == %{remote_ip}\nremote_port == %{remote_port}\nlocal ip == %{local_ip}\ntime_connect == %{time_connect}\n' + + + +# Verify data after the test has been "shot" + + +28 + + +s/time_connect == (?!0\.0+$)\d+\.\d+$/time_connect == [non-zero]/ + + +remote_ip == %HOSTIP +remote_port == %HTTPPORT +local ip == 127.0.0.1 +time_connect == [non-zero] + + + diff --git a/tests/data/test5030 b/tests/data/test5030 new file mode 100644 index 000000000000..dd187825d013 --- /dev/null +++ b/tests/data/test5030 @@ -0,0 +1,47 @@ + + + + +HTTPS +FTP + + + +# Server-side + + + +# Client-side + + +SSL + + +ftp + + +-w IP info and connect time after failed TLS handshake + +# The FTP server sends its plain text greeting, so the TCP connect +# succeeds and the TLS handshake fails + +-k https://%HOSTIP:%FTPPORT/%TESTNUMBER -w 'remote_ip == %{remote_ip}\nremote_port == %{remote_port}\nlocal ip == %{local_ip}\ntime_connect == %{time_connect}\n' + + + +# Verify data after the test has been "shot" + + +35 + + +s/time_connect == (?!0\.0+$)\d+\.\d+$/time_connect == [non-zero]/ + + +remote_ip == %HOSTIP +remote_port == %FTPPORT +local ip == 127.0.0.1 +time_connect == [non-zero] + + + From 24dfe3169c7f8e187ae01cd0e4b12cc3abb774dc Mon Sep 17 00:00:00 2001 From: Joshua Rogers Date: Thu, 8 Oct 2026 22:41:16 -0700 Subject: [PATCH 443/481] cfilters: fail a send that reports more written than requested Curl_conn_send() now returns CURLE_SEND_ERROR when a filter sets *pnwritten above the given length, instead of passing the bogus count up. Ref: #23260 Closes #23324 --- lib/cfilters.c | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/lib/cfilters.c b/lib/cfilters.c index 5e9cae343463..3e28f8919ebb 100644 --- a/lib/cfilters.c +++ b/lib/cfilters.c @@ -1038,9 +1038,16 @@ CURLcode Curl_conn_send(struct Curl_easy *data, int8_t sockindex, } } #endif - if(data && data->conn && data->conn->send[sockindex]) - return data->conn->send[sockindex](data, sockindex, buf, write_len, eos, - pnwritten); + if(data && data->conn && data->conn->send[sockindex]) { + CURLcode result = data->conn->send[sockindex](data, sockindex, buf, + write_len, eos, pnwritten); + if(!result && (*pnwritten > write_len)) { + DEBUGASSERT(0); + *pnwritten = 0; + return CURLE_SEND_ERROR; + } + return result; + } *pnwritten = 0; return CURLE_FAILED_INIT; } From 1536ae8e31574b75d9cd2ac978f5c781bda7f549 Mon Sep 17 00:00:00 2001 From: Stefan Eissing Date: Thu, 8 Oct 2026 16:17:20 +0200 Subject: [PATCH 444/481] pytest: fixture fixes - add danted dependency on httpd fixture - make dnsd fixture autostart Closes #23313 --- tests/http/conftest.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/http/conftest.py b/tests/http/conftest.py index 54af4e855b3c..4e6bdd8163d7 100644 --- a/tests/http/conftest.py +++ b/tests/http/conftest.py @@ -105,7 +105,7 @@ def env(pytestconfig, env_config) -> Env: @pytest.fixture(scope='session') -def danted(env) -> Generator[Union[Dante, bool], None, None]: +def danted(env, httpd) -> Generator[Union[Dante, bool], None, None]: if Env.has_danted(): danted = Dante(env=env) assert danted.initial_start() @@ -115,7 +115,7 @@ def danted(env) -> Generator[Union[Dante, bool], None, None]: yield False -@pytest.fixture(scope='session') +@pytest.fixture(scope='session', autouse=True) def dnsd(env) -> Generator[Dnsd, None, None]: dnsd = Dnsd(env=env) assert dnsd.initial_start() From 67662e2bef11437ae5a9285b14b194d2371ba330 Mon Sep 17 00:00:00 2001 From: Daniel Stenberg Date: Fri, 9 Oct 2026 09:33:35 +0200 Subject: [PATCH 445/481] mailmap: join Joushua's addresses --- .mailmap | 1 + 1 file changed, 1 insertion(+) diff --git a/.mailmap b/.mailmap index 22b0f256b8ce..c2bade7d0d97 100644 --- a/.mailmap +++ b/.mailmap @@ -125,3 +125,4 @@ Kaixuan Li Darren Banfi Alhuda Khan Ralf Mueller <217359725+hunterinvariants@users.noreply.github.com> +Joshua Rogers From f8423b9780dae1ce38bc2fcbcc6def5a9c3cea7d Mon Sep 17 00:00:00 2001 From: Daniel Stenberg Date: Fri, 9 Oct 2026 09:46:05 +0200 Subject: [PATCH 446/481] THANKS-filter: drop "OSS-Fuzz", it is a service not someone to credit --- docs/THANKS-filter | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/THANKS-filter b/docs/THANKS-filter index 36cac5d9cb42..bcf6055b2cda 100644 --- a/docs/THANKS-filter +++ b/docs/THANKS-filter @@ -164,3 +164,4 @@ s/Viktor Szakatas/Viktor Szakats/ s/Val S\./Valerie Snyder/ s/Andrew Nesbit$/Andrew Nesbitt/ s/^https:\/\/.*// +s/OSS-Fuzz// From 048ebab40910e27fbe9e9528f163f9dd402bbfa6 Mon Sep 17 00:00:00 2001 From: Daniel Stenberg Date: Fri, 9 Oct 2026 10:19:50 +0200 Subject: [PATCH 447/481] cf-ngtcp2-cmn: drop debug output Follow-up to 1cd267139e2 Closes #23328 --- lib/vquic/cf-ngtcp2-cmn.c | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/vquic/cf-ngtcp2-cmn.c b/lib/vquic/cf-ngtcp2-cmn.c index f81c67cc1f6a..e11cf734c26d 100644 --- a/lib/vquic/cf-ngtcp2-cmn.c +++ b/lib/vquic/cf-ngtcp2-cmn.c @@ -170,7 +170,6 @@ struct Curl_easy *Curl_cf_ngtcp2_get_xfer(struct Curl_cfilter *cf, data = Curl_multi_get_easy(call_data->multi, stream->mid); if(data && (data->id == stream->xfer_id)) return data; - curl_mfprintf(stderr, "H3 stream xfer is NULL\n"); DEBUGASSERT(0); } return NULL; From d37d79df1e01066417aff0a9e6bc675edad3edba Mon Sep 17 00:00:00 2001 From: Stefan Eissing Date: Fri, 9 Oct 2026 09:51:12 +0200 Subject: [PATCH 448/481] url: CURLOPT_INTERFACE fix connection reuse match Remember the full option string at the connection for use in reuse matching and cf-socket binding. Parse the string when set for syntax check, but remember the unparsed version and use that in connection matching. Parse that string again when the socket is set up. Closes #23326 --- lib/cf-socket.c | 133 +++++++++++++++++++++++++++++++----------------- lib/setopt.c | 30 +++-------- lib/url.c | 40 +++++++++------ lib/urldata.h | 6 +-- 4 files changed, 121 insertions(+), 88 deletions(-) diff --git a/lib/cf-socket.c b/lib/cf-socket.c index bc6edf28c97a..68f7f88e9d44 100644 --- a/lib/cf-socket.c +++ b/lib/cf-socket.c @@ -585,11 +585,13 @@ int Curl_socket_close(struct Curl_easy *data, struct connectdata *conn, * len [in] - length of the input string. * dev [in/out] - address where a pointer to newly allocated memory * holding the interface-or-host will be stored upon - * completion. + * completion or NULL if the caller is not interested. * iface [in/out] - address where a pointer to newly allocated memory - * holding the interface will be stored upon completion. + * holding the interface will be stored upon completion + * or NULL if the caller is not interested. * host [in/out] - address where a pointer to newly allocated memory - * holding the host will be stored upon completion. + * holding the host will be stored upon completion + * or NULL if the caller is not interested. * * Returns CURLE_OK on success. */ @@ -601,27 +603,36 @@ CURLcode Curl_parse_interface(const char *input, static const char if_host_prefix[] = "ifhost!"; size_t len; - DEBUGASSERT(dev); - DEBUGASSERT(iface); - DEBUGASSERT(host); - len = strlen(input); if(len > 512) return CURLE_BAD_FUNCTION_ARGUMENT; + if(dev) + *dev = NULL; + if(iface) + *iface = NULL; + if(host) + *host = NULL; + if(!strncmp(if_prefix, input, strlen(if_prefix))) { input += strlen(if_prefix); if(!*input) return CURLE_BAD_FUNCTION_ARGUMENT; - *iface = curlx_memdup0(input, len - strlen(if_prefix)); - return *iface ? CURLE_OK : CURLE_OUT_OF_MEMORY; + if(iface) { + *iface = curlx_memdup0(input, len - strlen(if_prefix)); + return *iface ? CURLE_OK : CURLE_OUT_OF_MEMORY; + } + return CURLE_OK; } else if(!strncmp(host_prefix, input, strlen(host_prefix))) { input += strlen(host_prefix); if(!*input) return CURLE_BAD_FUNCTION_ARGUMENT; - *host = curlx_memdup0(input, len - strlen(host_prefix)); - return *host ? CURLE_OK : CURLE_OUT_OF_MEMORY; + if(host) { + *host = curlx_memdup0(input, len - strlen(host_prefix)); + return *host ? CURLE_OK : CURLE_OUT_OF_MEMORY; + } + return CURLE_OK; } else if(!strncmp(if_host_prefix, input, strlen(if_host_prefix))) { const char *host_part; @@ -630,22 +641,30 @@ CURLcode Curl_parse_interface(const char *input, host_part = memchr(input, '!', len); if(!host_part || !*(host_part + 1)) return CURLE_BAD_FUNCTION_ARGUMENT; - *iface = curlx_memdup0(input, host_part - input); - if(!*iface) - return CURLE_OUT_OF_MEMORY; + if(iface) { + *iface = curlx_memdup0(input, host_part - input); + if(!*iface) + return CURLE_OUT_OF_MEMORY; + } ++host_part; - *host = curlx_memdup0(host_part, len - (host_part - input)); - if(!*host) { - curlx_safefree(*iface); - return CURLE_OUT_OF_MEMORY; + if(host) { + *host = curlx_memdup0(host_part, len - (host_part - input)); + if(!*host) { + if(iface) + curlx_safefree(*iface); + return CURLE_OUT_OF_MEMORY; + } } return CURLE_OK; } if(!*input) return CURLE_BAD_FUNCTION_ARGUMENT; - *dev = curlx_memdup0(input, len); - return *dev ? CURLE_OK : CURLE_OUT_OF_MEMORY; + if(dev) { + *dev = curlx_memdup0(input, len); + return *dev ? CURLE_OK : CURLE_OUT_OF_MEMORY; + } + return CURLE_OK; } #ifndef CURL_DISABLE_BINDLOCAL @@ -660,34 +679,42 @@ static CURLcode bindlocal(struct Curl_easy *data, struct connectdata *conn, #ifdef USE_IPV6 struct sockaddr_in6 *si6 = (struct sockaddr_in6 *)&sa; #endif - struct Curl_dns_entry *h = NULL; unsigned short port = data->set.localport; /* use this port number, 0 for "random" */ /* how many port numbers to try to bind to, increasing one at a time */ int portnum = data->set.localportrange; - const char *dev = CURL_EASY_STR(data, STRING_DEVICE); - const char *iface_input = CURL_EASY_STR(data, STRING_INTERFACE); - const char *host_input = CURL_EASY_STR(data, STRING_BINDHOST); - const char *iface = iface_input ? iface_input : dev; - const char *host = host_input ? host_input : dev; + char *dev_in = NULL, *iface_in = NULL, *host_in = NULL; + const char *iface = NULL, *host = NULL; int sockerr; #ifdef IP_BIND_ADDRESS_NO_PORT int on = 1; #endif + CURLcode result = CURLE_OK; + #ifndef USE_IPV6 (void)scope; #endif + if(conn->interface_opts) { + /************************************************************* + * Select device to bind socket to + *************************************************************/ + result = Curl_parse_interface(conn->interface_opts, + &dev_in, &iface_in, &host_in); + if(result) + goto out; + /* This is how we use the values */ + iface = iface_in ? iface_in : dev_in; + host = host_in ? host_in : dev_in; + } - /************************************************************* - * Select device to bind socket to - *************************************************************/ if(!iface && !host && !port) - /* no local kind of binding was requested */ - return CURLE_OK; - else if(iface && (strlen(iface) >= 255)) - return CURLE_BAD_FUNCTION_ARGUMENT; + goto out; + if(iface && (strlen(iface) >= 255)) { + result = CURLE_BAD_FUNCTION_ARGUMENT; + goto out; + } memset(&sa, 0, sizeof(struct Curl_sockaddr_storage)); if(iface || host) { @@ -714,12 +741,13 @@ static CURLcode bindlocal(struct Curl_easy *data, struct connectdata *conn, * succeeds it means the parameter was a valid interface and not an IP * address. Return immediately. */ - !host_input) { + !host_in) { infof(data, "socket successfully bound to interface '%s'", iface); - return CURLE_OK; + result = CURLE_OK; + goto out; } #endif - if(!host_input) { + if(!host_in) { /* Discover IP from input device, then bind to it */ if2ip_result = Curl_if2ip(af, #ifdef USE_IPV6 @@ -729,18 +757,20 @@ static CURLcode bindlocal(struct Curl_easy *data, struct connectdata *conn, } switch(if2ip_result) { case IF2IP_NOT_FOUND: - if(iface_input && !host_input) { + if(iface_in && !host_in) { /* Do not fall back to treating it as a hostname */ char buffer[STRERROR_LEN]; data->state.os_errno = sockerr = SOCKERRNO; failf(data, "Could not bind to interface '%s' with errno %d: %s", iface, sockerr, curlx_strerror(sockerr, buffer, sizeof(buffer))); - return CURLE_INTERFACE_FAILED; + result = CURLE_INTERFACE_FAILED; + goto out; } break; case IF2IP_AF_NOT_SUPPORTED: /* Signal the caller to try another address family if available */ - return CURLE_UNSUPPORTED_PROTOCOL; + result = CURLE_UNSUPPORTED_PROTOCOL; + goto out; case IF2IP_FOUND: /* * We now have the numerical IP address in the 'myhost' buffer @@ -751,7 +781,7 @@ static CURLcode bindlocal(struct Curl_easy *data, struct connectdata *conn, done = 1; break; } - if(!iface_input || host_input) { + if(!iface_in || host_in) { /* * This was not an interface, resolve the name as a hostname * or IP number @@ -778,7 +808,8 @@ static CURLcode bindlocal(struct Curl_easy *data, struct connectdata *conn, if(af != h_af) { /* bad IP version combo, signal the caller to try another address family if available */ - return CURLE_UNSUPPORTED_PROTOCOL; + result = CURLE_UNSUPPORTED_PROTOCOL; + goto out; } done = 1; } @@ -811,8 +842,10 @@ static CURLcode bindlocal(struct Curl_easy *data, struct connectdata *conn, if present, known to be numeric */ curl_off_t scope_id; if(curlx_str_number((const char **)CURL_UNCONST(&scope_ptr), - &scope_id, UINT_MAX)) - return CURLE_UNSUPPORTED_PROTOCOL; + &scope_id, UINT_MAX)) { + result = CURLE_UNSUPPORTED_PROTOCOL; + goto out; + } si6->sin6_scope_id = (unsigned int)scope_id; } #endif @@ -839,7 +872,8 @@ static CURLcode bindlocal(struct Curl_easy *data, struct connectdata *conn, data->state.os_errno = sockerr = SOCKERRNO; failf(data, "Could not bind to '%s' with errno %d: %s", host, sockerr, curlx_strerror(sockerr, buffer, sizeof(buffer))); - return CURLE_INTERFACE_FAILED; + result = CURLE_INTERFACE_FAILED; + goto out; } } else { @@ -866,7 +900,8 @@ static CURLcode bindlocal(struct Curl_easy *data, struct connectdata *conn, /* we succeeded to bind */ infof(data, "Local port: %hu", port); conn->bits.bound = TRUE; - return CURLE_OK; + result = CURLE_OK; + goto out; } if(--portnum > 0) { @@ -892,7 +927,13 @@ static CURLcode bindlocal(struct Curl_easy *data, struct connectdata *conn, sockerr, curlx_strerror(sockerr, buffer, sizeof(buffer))); } - return CURLE_INTERFACE_FAILED; + result = CURLE_INTERFACE_FAILED; + +out: + curlx_free(dev_in); + curlx_free(iface_in); + curlx_free(host_in); + return result; } #endif diff --git a/lib/setopt.c b/lib/setopt.c index 754bb2240e16..cbfeb7ca5f9e 100644 --- a/lib/setopt.c +++ b/lib/setopt.c @@ -154,32 +154,18 @@ static CURLcode setstropt_userpwd(const char *option, char **userp, static CURLcode setstropt_interface(struct Curl_easy *data, char *option) { - char *dev = NULL; - char *iface = NULL; - char *host = NULL; - CURLcode result; - + if(option && !option[0]) + option = NULL; if(option) { - /* Parse the interface details if set, otherwise clear them all */ - result = Curl_parse_interface(option, &dev, &iface, &host); + /* Parse for checking syntax */ + CURLcode result = Curl_parse_interface(option, NULL, NULL, NULL); if(result) return result; + return CURL_EASY_STR_SET(data, STRING_INTERFACE_OPT, + option, strlen(option)); } - - result = CURL_EASY_STR_SETN(data, STRING_DEVICE, dev); - dev = NULL; - if(!result) { - result = CURL_EASY_STR_SETN(data, STRING_INTERFACE, iface); - iface = NULL; - } - if(!result) { - result = CURL_EASY_STR_SETN(data, STRING_BINDHOST, host); - host = NULL; - } - curlx_free(dev); - curlx_free(iface); - curlx_free(host); - return result; + CURL_EASY_STR_CLEAR(data, STRING_INTERFACE_OPT); + return CURLE_OK; } #ifdef USE_SSL diff --git a/lib/url.c b/lib/url.c index a474c76538e7..b189d52841b2 100644 --- a/lib/url.c +++ b/lib/url.c @@ -513,7 +513,7 @@ void Curl_conn_free(struct Curl_easy *data, struct connectdata *conn) Curl_creds_unlink(&conn->creds); Curl_peer_unlink(&conn->creds_origin); curlx_safefree(conn->options); - curlx_safefree(conn->localdev); + curlx_safefree(conn->interface_opts); Curl_ssl_conn_config_cleanup(conn); curlx_safefree(conn->destination); @@ -615,21 +615,28 @@ static bool url_match_connect_config(struct connectdata *conn, m->data->set.ipver != conn->ip_version) return FALSE; - if((m->needle->localdev || m->needle->localport) && + /* When needle has specific local bind settings, those have to match. + * If it does not, any other connection might do when it comes to ports. */ + if(m->needle->localport && + ((conn->localport != m->needle->localport) || + (conn->localportrange != m->needle->localportrange))) + return FALSE; + /* For interface related bind options, we require a full match + * as connection might go totally different ways (VPNs, for example). */ + if((m->needle->interface_opts != conn->interface_opts) && + (!m->needle->interface_opts || !conn->interface_opts || + strcmp(conn->interface_opts, m->needle->interface_opts))) + return FALSE; + + if((m->needle->interface_opts || m->needle->localport) && /* If we are bound to a specific local end (IP+port), we must not reuse a random other one, although if we did not ask for a particular one we - can reuse one that was bound. - - This comparison is a bit rough and too strict. Since the input - parameters can be specified in numerous ways and still end up the same - it would take a lot of processing to make it really accurate. Instead, - this matching will assume that reuses of bound connections will most - likely also reuse the exact same binding parameters and missing out a - few edge cases should not hurt anyone much. */ + can reuse one that was bound. */ ((conn->localport != m->needle->localport) || (conn->localportrange != m->needle->localportrange) || - (m->needle->localdev && - (!conn->localdev || strcmp(conn->localdev, m->needle->localdev))))) + (m->needle->interface_opts && + (!conn->interface_opts || + strcmp(conn->interface_opts, m->needle->interface_opts))))) return FALSE; if(!m->needle->via_peer != !conn->via_peer) @@ -1144,9 +1151,10 @@ static struct connectdata *allocate_conn(struct Curl_easy *data) conn->transport_wanted = TRNSPRT_TCP; /* most of them are TCP streams */ /* Store the local bind parameters that will be used for this connection */ - if(CURL_EASY_STR(data, STRING_DEVICE)) { - conn->localdev = curlx_strdup(CURL_EASY_STR(data, STRING_DEVICE)); - if(!conn->localdev) + if(CURL_EASY_STR(data, STRING_INTERFACE_OPT)) { + conn->interface_opts = + curlx_strdup(CURL_EASY_STR(data, STRING_INTERFACE_OPT)); + if(!conn->interface_opts) goto error; } #ifndef CURL_DISABLE_BINDLOCAL @@ -1164,7 +1172,7 @@ static struct connectdata *allocate_conn(struct Curl_easy *data) return conn; error: - curlx_free(conn->localdev); + curlx_free(conn->interface_opts); curlx_free(conn); return NULL; } diff --git a/lib/urldata.h b/lib/urldata.h index 6880783b5f35..d69233641e25 100644 --- a/lib/urldata.h +++ b/lib/urldata.h @@ -334,7 +334,7 @@ struct connectdata { made and will serve the purpose of being used for comparison reasons so that subsequent bound-requested connections are not accidentally reusing wrong connections. */ - char *localdev; + char *interface_opts; struct ConnectBits bits; /* various state-flags for this connection */ #if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI) int socks5_gssapi_enctype; @@ -727,9 +727,7 @@ enum dupstring { #endif STRING_CUSTOMREQUEST, /* HTTP/FTP/RTSP request/method to use */ STRING_DEFAULT_PROTOCOL, /* Protocol to use when the URL does not specify */ - STRING_DEVICE, /* local network interface/address to use */ - STRING_INTERFACE, /* local network interface to use */ - STRING_BINDHOST, /* local address to use */ + STRING_INTERFACE_OPT, /* CURLOPT_INTERFACE config */ STRING_ENCODING, /* Accept-Encoding string */ #ifndef CURL_DISABLE_FTP STRING_FTP_ACCOUNT, /* ftp account data */ From e94fb70ae0286c3e9ca9331f95ada7201b0afb04 Mon Sep 17 00:00:00 2001 From: Stefan Eissing Date: Fri, 9 Oct 2026 10:46:18 +0200 Subject: [PATCH 449/481] h3-proxy: teardown fix Move the code that tears down nghttp3 and ngtcp2 connection instance into the general cleanup, so h3 proxy code also does this. Fixes #23327 Reported-by: Max Dymond Closes #23330 --- lib/vquic/cf-ngtcp2-cmn.c | 24 ++++++++++++++++++++---- lib/vquic/cf-ngtcp2-proxy.c | 6 ++++++ lib/vquic/cf-ngtcp2.c | 31 ------------------------------- 3 files changed, 26 insertions(+), 35 deletions(-) diff --git a/lib/vquic/cf-ngtcp2-cmn.c b/lib/vquic/cf-ngtcp2-cmn.c index e11cf734c26d..12a4d2b06b19 100644 --- a/lib/vquic/cf-ngtcp2-cmn.c +++ b/lib/vquic/cf-ngtcp2-cmn.c @@ -138,18 +138,34 @@ CURLcode Curl_cf_ngtcp2_ctx_init(struct cf_ngtcp2_ctx *ctx, void Curl_cf_ngtcp2_ctx_cleanup(struct cf_ngtcp2_ctx *ctx) { if(ctx && ctx->initialized) { + if(ctx->qlogfd != -1) { + curlx_close(ctx->qlogfd); + ctx->qlogfd = -1; + } + /* Cleanup TLS instances first as ngtcp2 objects + * might be registered there. */ Curl_vquic_tls_cleanup(&ctx->tls); Curl_vquic_ctx_free(&ctx->q); + if(ctx->h3conn) { + nghttp3_conn_del(ctx->h3conn); + ctx->h3conn = NULL; + } + if(ctx->qconn) { + ngtcp2_conn_del(ctx->qconn); + ctx->qconn = NULL; + } +#ifdef OPENSSL_QUIC_API2 + if(ctx->ossl_ctx) { + ngtcp2_crypto_ossl_ctx_del(ctx->ossl_ctx); + ctx->ossl_ctx = NULL; + } +#endif Curl_bufcp_free(&ctx->stream_bufcp); curlx_dyn_free(&ctx->scratch); Curl_u32_ptrset_clear(&ctx->streams); Curl_ssl_peer_cleanup(&ctx->ssl_peer); curlx_safefree(ctx->tunnel_inbuf); ctx->tunnel_inbuf_len = 0; - if(ctx->qlogfd != -1) { - curlx_close(ctx->qlogfd); - ctx->qlogfd = -1; - } } } diff --git a/lib/vquic/cf-ngtcp2-proxy.c b/lib/vquic/cf-ngtcp2-proxy.c index 1510436cf309..043ee3c6a788 100644 --- a/lib/vquic/cf-ngtcp2-proxy.c +++ b/lib/vquic/cf-ngtcp2-proxy.c @@ -1186,6 +1186,12 @@ static void cf_h3_proxy_destroy(struct Curl_cfilter *cf, (void)data; if(ctx) { CURL_TRC_CF(data, cf, "cf_h3_proxy_destroy()"); + if(ctx->ngtcp2_ctx.qconn) { + struct cf_call_data save; + CF_DATA_SAVE(save, cf, data); + Curl_cf_ngtcp2_cmn_conn_close(cf, data); + CF_DATA_RESTORE(cf, save); + } cf_h3_proxy_ctx_free(ctx); cf->ctx = NULL; } diff --git a/lib/vquic/cf-ngtcp2.c b/lib/vquic/cf-ngtcp2.c index 1620725ea13b..8cda37656da4 100644 --- a/lib/vquic/cf-ngtcp2.c +++ b/lib/vquic/cf-ngtcp2.c @@ -966,36 +966,6 @@ static CURLcode cf_ngtcp2_cntrl(struct Curl_cfilter *cf, return result; } -static void cf_ngtcp2_ctx_close(struct cf_ngtcp2_ctx *ctx) -{ - struct cf_call_data save = ctx->call_data; - - if(!ctx->initialized) - return; - if(ctx->qlogfd != -1) { - curlx_close(ctx->qlogfd); - } - ctx->qlogfd = -1; - Curl_vquic_tls_cleanup(&ctx->tls); - Curl_ssl_peer_cleanup(&ctx->ssl_peer); - Curl_vquic_ctx_free(&ctx->q); - if(ctx->h3conn) { - nghttp3_conn_del(ctx->h3conn); - ctx->h3conn = NULL; - } - if(ctx->qconn) { - ngtcp2_conn_del(ctx->qconn); - ctx->qconn = NULL; - } -#ifdef OPENSSL_QUIC_API2 - if(ctx->ossl_ctx) { - ngtcp2_crypto_ossl_ctx_del(ctx->ossl_ctx); - ctx->ossl_ctx = NULL; - } -#endif - ctx->call_data = save; -} - static void cf_ngtcp2_destroy(struct Curl_cfilter *cf, struct Curl_easy *data) { struct cf_ngtcp2_ctx *ctx = cf->ctx; @@ -1006,7 +976,6 @@ static void cf_ngtcp2_destroy(struct Curl_cfilter *cf, struct Curl_easy *data) struct cf_call_data save; CF_DATA_SAVE(save, cf, data); Curl_cf_ngtcp2_cmn_conn_close(cf, data); - cf_ngtcp2_ctx_close(ctx); CF_DATA_RESTORE(cf, save); } Curl_cf_ngtcp2_ctx_cleanup(ctx); From c00a3021e4b833afebed33f31a8f006a1b7511fe Mon Sep 17 00:00:00 2001 From: ns-ylambert Date: Mon, 5 Oct 2026 18:27:15 +0200 Subject: [PATCH 450/481] mbedtls: pass correct length for PEM/DER key blobs to mbedtls_pk_parse_key `curlx_memdup0()` returns the key blob NUL-terminated with a total length of `blob->len + 1`, but `mbedtls_pk_parse_key()` was called with the original `blob->len`, which excludes the terminator. For a PEM key, `mbedtls_pk_parse_key()` requires the terminator to be within the given length (every PEM path is guarded by a check that `key[keylen - 1]` is `'\0'`), so parsing always failed with `MBEDTLS_ERR_PK_KEY_INVALID_FORMAT` (-0x3D00) and client auth via `CURLOPT_SSLKEY_BLOB` never worked on mbedTLS. However, the terminator must only be counted in for PEM: the DER parsers require the exact length and reject a key with the trailing NUL counted in with `MBEDTLS_ERR_ASN1_LENGTH_MISMATCH` (-0x1600). The PKCS#1/SEC1 fallback parsers cannot reinterpret a PKCS#8 wrapper, so passing `blob->len + 1` unconditionally makes all DER key blobs fail. Make the length conditional on `CURLOPT_SSLKEYTYPE`: PEM (the default) keeps the terminator counted in, explicit DER passes the exact blob length, matching the DER handling of the CA and client-cert blob paths. Closes #23274 --- lib/vtls/mbedtls.c | 11 +++++++++-- tests/data/test2095 | 1 - 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/lib/vtls/mbedtls.c b/lib/vtls/mbedtls.c index e74be22ef21e..c7cfc005da2c 100644 --- a/lib/vtls/mbedtls.c +++ b/lib/vtls/mbedtls.c @@ -701,16 +701,23 @@ static CURLcode mbed_load_privkey(struct Curl_cfilter *cf, else { const struct curl_blob *ssl_key_blob = conn_config->key_blob; const char *passwd = conn_config->key_passwd; + const bool is_der = conn_config->key_type && + curl_strequal(conn_config->key_type, "DER"); /* Unfortunately, mbedtls_pk_parse_key() requires the data to be null-terminated if the data is PEM encoded (even when provided the exact length). */ unsigned char *newblob = curlx_memdup0(ssl_key_blob->data, ssl_key_blob->len); + /* mbedtls_pk_parse_key() requires the terminator to be counted in the + length for PEM input, while the DER parsers require the exact + length: a DER key with the terminator counted in is rejected with + MBEDTLS_ERR_ASN1_LENGTH_MISMATCH. */ + size_t keylen = is_der ? ssl_key_blob->len : ssl_key_blob->len + 1; if(!newblob) return CURLE_OUT_OF_MEMORY; #if MBEDTLS_VERSION_NUMBER >= 0x04000000 - ret = mbedtls_pk_parse_key(&backend->pk, newblob, ssl_key_blob->len, + ret = mbedtls_pk_parse_key(&backend->pk, newblob, keylen, (const unsigned char *)passwd, passwd ? strlen(passwd) : 0); if(ret == 0 && @@ -722,7 +729,7 @@ static CURLcode mbed_load_privkey(struct Curl_cfilter *cf, PSA_KEY_USAGE_SIGN_HASH))) ret = MBEDTLS_ERR_PK_TYPE_MISMATCH; #else - ret = mbedtls_pk_parse_key(&backend->pk, newblob, ssl_key_blob->len, + ret = mbedtls_pk_parse_key(&backend->pk, newblob, keylen, (const unsigned char *)passwd, passwd ? strlen(passwd) : 0, mbedtls_ctr_drbg_random, diff --git a/tests/data/test2095 b/tests/data/test2095 index 06acabbb0e5f..cb1736fcb3d5 100644 --- a/tests/data/test2095 +++ b/tests/data/test2095 @@ -27,7 +27,6 @@ MooMoo SSL !Schannel !GnuTLS -!mbedtls local-http From 686ec81f251e01ba45eff1f2e3fbb5c387b11e88 Mon Sep 17 00:00:00 2001 From: Daniel Stenberg Date: Fri, 9 Oct 2026 15:55:41 +0200 Subject: [PATCH 451/481] http_chunks: accept exactly 17 characters chunk-size On all systems, not just 64-bit: clarify the comment. Ref: #23333 Closes #23334 --- lib/http_chunks.h | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/http_chunks.h b/lib/http_chunks.h index 8f6086b61e94..00a27cc807e9 100644 --- a/lib/http_chunks.h +++ b/lib/http_chunks.h @@ -33,10 +33,11 @@ struct connectdata; /* * The longest possible hexadecimal number we support in a chunked transfer. - * Neither RFC2616 nor the later HTTP specs define a maximum chunk size. - * For 64-bit curl_off_t we support 16 digits. For 32-bit, 8 digits. + * Neither RFC2616 nor the later HTTP specs define a maximum chunk size. With + * the 64-bit curl_off_t we support 16 digits, excluding leading zeroes. But + * why would a sane server prefix with zeroes? Let's accept one. */ -#define CHUNK_MAXNUM_LEN (SIZEOF_CURL_OFF_T * 2) +#define CHUNK_MAXNUM_LEN 17 typedef enum { /* await and buffer all hexadecimal digits until we get one that is not a From d3e7cafc06f425b6a6ad0989867bb2a8e8afbd9f Mon Sep 17 00:00:00 2001 From: Viktor Szakats Date: Fri, 9 Oct 2026 14:39:28 +0200 Subject: [PATCH 452/481] GHA/windows: drop unused `-ac` runtests options The external curl copy passed as its argument is used to access CI APIs. It's a no-op outside AppVeyor CI and Azure CI. Cherry-picked from #23306 Closes #23335 --- .github/workflows/windows.yml | 9 --------- 1 file changed, 9 deletions(-) diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index aa3a0b36cca8..eeedeeedcbf3 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -218,9 +218,6 @@ jobs: run: | PATH=/usr/bin:/cygdrive/c/my-stunnel/bin TFLAGS="-j8 ${TFLAGS}" - if [ -x "$(cygpath "${SYSTEMROOT}/System32/curl.exe")" ]; then - TFLAGS+=" -ac $(cygpath "${SYSTEMROOT}/System32/curl.exe")" - fi if [ "${MATRIX_BUILD}" = 'cmake' ]; then PATH="$PWD/bld/lib:$PATH" cmake --build bld --verbose --target test-ci @@ -558,9 +555,6 @@ jobs: PATH="/c/OpenSSH-Win64:$PATH" fi fi - if [ -x "$(cygpath "${SYSTEMROOT}/System32/curl.exe")" ]; then - TFLAGS+=" -ac $(cygpath "${SYSTEMROOT}/System32/curl.exe")" - fi PATH="$PATH:/c/my-stunnel/bin" if [ "${MATRIX_BUILD}" = 'cmake' ]; then PATH="$PWD/bld/lib:$PATH" @@ -778,9 +772,6 @@ jobs: TFLAGS="-j8 ${TFLAGS}" TFLAGS+=' !498' # 'Reject too large HTTP response headers on endless redirects' HTTP, HTTP GET (runtests detecting result code 2009 instead of 56 returned by curl) TFLAGS+=' ~3000 ~3001 ~3023 ~3024' # 'HTTPS localhost, last subject alt name matches, CN does not match' HTTPS, HTTP GET, PEM certificate (returning 56) - if [ -x "$(cygpath "${SYSTEMROOT}/System32/curl.exe")" ]; then - TFLAGS+=" -ac $(cygpath "${SYSTEMROOT}/System32/curl.exe")" - fi PATH="$PWD/bld/lib:$PATH:/c/my-stunnel/bin" cmake --build bld --target test-ci From 098d3a0d4044d8a3f0a8617a5a5a30cde90fba26 Mon Sep 17 00:00:00 2001 From: Viktor Szakats Date: Fri, 9 Oct 2026 13:25:11 +0200 Subject: [PATCH 453/481] runtests: unset `CURL_MEMDEBUG` for curl requests made to the CI API runtests has an automatic feature which detects AppVeyor and Azure CI, and communicates runtests test progress and results through the CI API. This feature uses the curl tool to send the API requests. It offers the `-ac` TFLAGS option to configure an external curl tool for these requests, otherwise it uses the freshly built curl tool. This curl is invoked from the same environment used to invoke the actual test curl commands and unit test binaries, thus it has `CURL_MEMDEBUG` set and pointing to a test-specific log file. The issue arises when the curl tool making the API request happens to be a debug-enabled one, with the 'TrackMemory' feature enabled. Such curl picks up the `CURL_MEMDEBUG` env and dumps it's own, memory tracing output into the test's log file. This causes flaky but consistent test failures, with bizarre errors, reporting corrupt or unsupported lines, and/or just not matching the expected output. When relying on the freshly built curl (without the `-ac` option) this is guaranteed to happen for all debug-enabled builds. As I found out the hard way, AppVeyer CI Windows 2012/2016 machines come preinstalled with an MSYS2 curl, which has _TrackMemory enabled_, which was explicitly set to fix this issue, and caused the very same problem. To fix, ensure to unset `CURL_MEMDEBUG` before making the API request with curl, avoiding this issue with all curl builds. It fixes future suprises, and also makes the freshly built curl a viable option in most cases, even in debug-enabled builds. (the requirement is to have working, cleartext HTTP support; for the AppVeyor API.) Example fails: ``` ** MEMORY FAILURE Not recognized prefix line: SEND sendf.c:391 send(424) = 424 Not recognized prefix line: RECV sendf.c:459 recv(102400) = 114 3216: memory FAILED [...] test 3306 [thrdqueue retries failed thread starts on receive] ** MEMORY FAILURE Not recognized prefix line: il)) [...] test 1607 [CURLOPT_RESOLVE parsing] ** MEMORY FAILURE Not recognized prefix line: 47 calloc(2,8) = 0xde7a13368 CUSTOMBUILD : FREE error : No memory allocated: MEM C:/projects/curl/lib/url.c:303 free(0xde7a145b8) CUSTOMBUILD : FREE error : No memory allocated: MEM C:/projects/curl/lib/url.c:3 ``` Notice, current curl no longer emits SEND/RECV log events. The line numbers match the source code of curl 7.65.3. The other two examples are foreign / corrupt log lines. The TrackMemory-enabled curl was shipping in MSYS2, and come preinstalled on these AppVeyor CI machine, as `C:\msys64\usr\bin\curl.exe`: - Visual Studio 2015 (Windows Server 2012 R2) - Visual Studio 2017 (Windows 2016) ``` curl 7.65.3 (x86_64-pc-msys) libcurl/7.65.3 OpenSSL/1.1.1c zlib/1.2.11 brotli/1.0.7 libidn2/2.2.0 libpsl/0.21.0 (+libidn2/2.2.0) libssh2/1.8.2 nghttp2/1.39.1 Release-Date: 2019-07-19 Protocols: dict file ftp ftps gopher http https imap imaps pop3 pop3s rtsp scp sftp smb smbs smtp smtps telnet tftp. Features: AsynchDNS brotli Debug GSS-API HTTP2 HTTPS-proxy IDN IPv6 Kerberos Largefile libz Metalink NTLM NTLM_WB PSL SPNEGO SSL TLS-SRP TrackMemory UnixSockets ``` Bug: https://github.com/curl/curl/pull/23306#issuecomment-6043152120 Bug: https://github.com/curl/curl/pull/23306#issuecomment-6048388544 Bug: https://github.com/curl/curl/pull/23306#issuecomment-6048790733 Bug: https://github.com/curl/curl/pull/23306#issuecomment-6079755601 Cherry-picked from #23306 Closes #23332 --- tests/appveyor.pm | 6 ++++++ tests/azure.pm | 8 ++++++++ 2 files changed, 14 insertions(+) diff --git a/tests/appveyor.pm b/tests/appveyor.pm index b3f8df9b3175..6ea2cccd3906 100644 --- a/tests/appveyor.pm +++ b/tests/appveyor.pm @@ -52,6 +52,8 @@ sub appveyor_create_test_result { $testname =~ s/\\/\\\\/g; $testname =~ s/\"/\\\"/g; $testname =~ s/\'/'"'"'/g; + local $ENV{CURL_MEMDEBUG}; + delete $ENV{CURL_MEMDEBUG}; my $appveyor_baseurl = $ENV{'APPVEYOR_API_URL'}; my $appveyor_result = qx($curl --silent --noproxy '*' \\ --header 'Content-Type: application/json' \\ @@ -96,6 +98,8 @@ sub appveyor_update_test_result { $appveyor_outcome = 'Failed'; $appveyor_category = 'Error'; } + local $ENV{CURL_MEMDEBUG}; + delete $ENV{CURL_MEMDEBUG}; my $appveyor_baseurl = $ENV{'APPVEYOR_API_URL'}; my $appveyor_result = qx($curl --silent --noproxy '*' --request PUT \\ --header 'Content-Type: application/json' \\ @@ -112,6 +116,8 @@ sub appveyor_update_test_result { '$appveyor_baseurl/api/tests'); print "AppVeyor API result: $appveyor_result\n" if($appveyor_result); if($appveyor_category eq 'Error') { + local $ENV{CURL_MEMDEBUG}; + delete $ENV{CURL_MEMDEBUG}; $appveyor_result = qx($curl --silent --noproxy '*' \\ --header 'Content-Type: application/json' \\ --data ' diff --git a/tests/azure.pm b/tests/azure.pm index e449a07fd485..623de969b98c 100644 --- a/tests/azure.pm +++ b/tests/azure.pm @@ -54,6 +54,8 @@ sub azure_check_environment { sub azure_create_test_run { my ($curl) = @_; + local $ENV{CURL_MEMDEBUG}; + delete $ENV{CURL_MEMDEBUG}; my $azure_baseurl = "$ENV{'SYSTEM_TEAMFOUNDATIONCOLLECTIONURI'}$ENV{'SYSTEM_TEAMPROJECTID'}"; my $azure_run = qx($curl --silent --noproxy "*" \\ --header "Authorization: Bearer $ENV{'AZURE_ACCESS_TOKEN'}" \\ @@ -77,6 +79,8 @@ sub azure_create_test_result { $testname =~ s/\\/\\\\/g; $testname =~ s/\"/\\\"/g; $testname =~ s/\'/'"'"'/g; + local $ENV{CURL_MEMDEBUG}; + delete $ENV{CURL_MEMDEBUG}; my $title_testnum = sprintf("%04d", $testnum); my $azure_baseurl = "$ENV{'SYSTEM_TEAMFOUNDATIONCOLLECTIONURI'}$ENV{'SYSTEM_TEAMPROJECTID'}"; my $azure_result = qx($curl --silent --noproxy '*' \\ @@ -122,6 +126,8 @@ sub azure_update_test_result { else { $azure_outcome = 'Failed'; } + local $ENV{CURL_MEMDEBUG}; + delete $ENV{CURL_MEMDEBUG}; my $azure_baseurl = "$ENV{'SYSTEM_TEAMFOUNDATIONCOLLECTIONURI'}$ENV{'SYSTEM_TEAMPROJECTID'}"; my $azure_result = qx($curl --silent --noproxy '*' --request PATCH \\ --header "Authorization: Bearer $ENV{'AZURE_ACCESS_TOKEN'}" \\ @@ -146,6 +152,8 @@ sub azure_update_test_result { sub azure_update_test_run { my ($curl, $azure_run_id) = @_; + local $ENV{CURL_MEMDEBUG}; + delete $ENV{CURL_MEMDEBUG}; my $azure_baseurl = "$ENV{'SYSTEM_TEAMFOUNDATIONCOLLECTIONURI'}$ENV{'SYSTEM_TEAMPROJECTID'}"; my $azure_run = qx($curl --silent --noproxy '*' --request PATCH \\ --header "Authorization: Bearer $ENV{'AZURE_ACCESS_TOKEN'}" \\ From 82aea8c0675c108a1f656e3a33c7fda09ba7aa50 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 10 Oct 2026 00:21:41 +0000 Subject: [PATCH 454/481] GHA: update dependency awslabs/aws-lc to v5.11.0, ruff to v0.17.0 Closes #23340 Closes #23341 --- .github/scripts/requirements.txt | 2 +- .github/workflows/http3-linux.yml | 2 +- .github/workflows/linux.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/scripts/requirements.txt b/.github/scripts/requirements.txt index 45a0fca0c740..9548cd23d233 100644 --- a/.github/scripts/requirements.txt +++ b/.github/scripts/requirements.txt @@ -5,4 +5,4 @@ cmakelang==0.6.13 codespell==2.4.3 reuse==6.2.0 -ruff==0.16.9 +ruff==0.17.0 diff --git a/.github/workflows/http3-linux.yml b/.github/workflows/http3-linux.yml index 23820b7793d7..2bfabaf05810 100644 --- a/.github/workflows/http3-linux.yml +++ b/.github/workflows/http3-linux.yml @@ -40,7 +40,7 @@ env: CURL_TEST_MIN: 2000 DO_NOT_TRACK: '1' # renovate: datasource=github-tags depName=awslabs/aws-lc - AWSLC_VERSION: 5.8.0 + AWSLC_VERSION: 5.11.0 # renovate: datasource=github-tags depName=google/boringssl BORINGSSL_VERSION: 0.20260929.0 # renovate: datasource=github-tags depName=gnutls/nettle versioning=semver-coerced extractVersion=^nettle_?(?.+)_release_.+$ diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 55145e2f1b60..93a9fd143860 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -38,7 +38,7 @@ env: CURL_TEST_MIN: 1800 DO_NOT_TRACK: '1' # renovate: datasource=github-tags depName=awslabs/aws-lc - AWSLC_VERSION: 5.8.0 + AWSLC_VERSION: 5.11.0 # renovate: datasource=github-tags depName=google/boringssl BORINGSSL_VERSION: 0.20260929.0 # renovate: datasource=github-releases depName=pizlonator/fil-c versioning=semver-coerced From 2f3f99da36f7cddbbf44939f992b9e3da6b2a75f Mon Sep 17 00:00:00 2001 From: Viktor Szakats Date: Fri, 9 Oct 2026 22:17:39 +0200 Subject: [PATCH 455/481] test1119, 1167: disable MSYS2 argument conversion To fix failing in all/most AppVeyor CI jobs regardless of VS or OS version. CMake 3.19.8-4.2.1 with Visual Studio generator. Fixing: ``` === Start of file stderr1119 Microsoft (R) C/C++ Optimizing Compiler Version 19.51.36248 for x64 Copyright (C) Microsoft Corporation. All rights reserved. DWIN32 c1 : fatal error C1083: Cannot open source file: 'C:/msys64/DWIN32': No such file or directory D_WINDOWS c1 : fatal error C1083: Cannot open source file: 'C:/msys64/D_WINDOWS': No such file or directory curl.h Error preprocessing C:/projects/curl/tests/../include/curl/curl.h at C:/projects/curl/tests/test1119.pl line 79. === End of file stderr1119 ``` Ref: https://ci.appveyor.com/project/curlorg/curl/builds/54868644/job/mxwd9iewudo1d60x?fullLog=true#L2189 `_bld/tests/configurehelp.pm` with VS2026, Debug, x64, !ssl, Static, Build-tests: ```Perl package configurehelp; [...] $Cpreprocessor = '"C:/Program Files/Microsoft Visual Studio/18/Community/VC/Tools/MSVC/14.51.36231/bin/Hostx64/x64/cl.exe" -E /DWIN32 /D_WINDOWS -IC:/projects/curl/include -IC:/projects/curl/_bld/lib -IC:/projects/curl/lib'; ``` Ref: https://ci.appveyor.com/project/curlorg/curl/builds/54869181/job/ssl908dghu6cu3ny?fullLog=true#L203 Ref: https://ci.appveyor.com/project/curlorg/curl/builds/54868644 Cherry-picked from #23306 Closes #23339 --- tests/test1119.pl | 1 + tests/test1167.pl | 1 + 2 files changed, 2 insertions(+) diff --git a/tests/test1119.pl b/tests/test1119.pl index a1b09d059457..d77cd2c8a0d7 100755 --- a/tests/test1119.pl +++ b/tests/test1119.pl @@ -66,6 +66,7 @@ # included by it, which *should* be all headers sub scanenum { my ($file) = @_; + local $ENV{'MSYS2_ARG_CONV_EXCL'} = '*'; open(my $h_in, "-|", "$Cpreprocessor $i$file") or die "Cannot preprocess $file"; while(<$h_in>) { if(/enum\s+(\S+\s+)?{/ .. /}/) { diff --git a/tests/test1167.pl b/tests/test1167.pl index a0bdb385db91..c3c87e6924c3 100755 --- a/tests/test1167.pl +++ b/tests/test1167.pl @@ -71,6 +71,7 @@ sub scanenums { my ($file) = @_; my $skipit = 0; + local $ENV{'MSYS2_ARG_CONV_EXCL'} = '*'; open(H_IN, "-|", "$Cpreprocessor -DCURL_DISABLE_DEPRECATION $i$file") or die "Cannot preprocess $file"; From ce0fe7124c2443f68a2501a557818e53e297fc61 Mon Sep 17 00:00:00 2001 From: Viktor Szakats Date: Sat, 10 Oct 2026 22:44:55 +0200 Subject: [PATCH 456/481] GHA/non-native: lower pccritic score for FreeBSD jobs FreeBSD 15.1 received a pkgconf package update from 2.4.3_1 to 3.0.7, which ships with pccritic and enabled its CI test in the autotools job, with this result: ``` bld/libcurl.pc score: 93/100 grade: A (0 critical, 0 major, 0 minor, 7 info) [info ] Libs.private adds a library path already on the default search path (pkgconf strips it): -L/usr/local/lib (libs/PC040) [info ] Libs.private adds a library path already on the default search path (pkgconf strips it): -L/usr/local/lib (libs/PC040) [info ] Libs.private adds a library path already on the default search path (pkgconf strips it): -L/usr/local/lib (libs/PC040) [info ] Libs.private adds a library path already on the default search path (pkgconf strips it): -L/usr/local/lib (libs/PC040) [info ] Libs.private adds a library path already on the default search path (pkgconf strips it): -L/usr/local/lib (libs/PC040) [info ] Libs.private adds a library path already on the default search path (pkgconf strips it): -L/usr/local/lib (libs/PC040) [info ] Libs.private adds a library path already on the default search path (pkgconf strips it): -L/usr/local/lib (libs/PC040) ``` due to this line `libcurl.pc`: ``` Libs.private: -L/usr/local/lib -L/usr/local/lib -L/usr/local/lib -L/usr/local/lib -L/usr/local/lib -L/usr/local/lib -lnghttp2 -lidn2 -lldap -llber -lssh2 -lssh2 -lpsl -lssl -lcrypto -lssl -lcrypto -L/usr/local/lib -lgssapi_krb5 -lzstd -lbrotlidec -lbrotlidec -lbrotlicommon -lz -pthread ``` Ref: https://github.com/curl/curl/actions/runs/38082969589/job/114303510971?pr=23345 Lower the minimum score till the real fix is found. Follow-up to 943ac04fc4d7d554d9064c7cd03fbe1830ff25d4 #22543 Closes #23346 --- .github/workflows/non-native.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/non-native.yml b/.github/workflows/non-native.yml index ad0b6c7b555c..b233955944a7 100644 --- a/.github/workflows/non-native.yml +++ b/.github/workflows/non-native.yml @@ -200,7 +200,11 @@ jobs: done if command -v pccritic >/dev/null 2>&1; then pccritic --version - pccritic --min-score 98 bld/libcurl.pc + if [ "${MATRIX_OS}" = 'freebsd' ]; then + pccritic --min-score 93 bld/libcurl.pc + else + pccritic --min-score 98 bld/libcurl.pc + fi fi - name: 'build' From b8f71d6e1199701352c498467c17847237d1c43e Mon Sep 17 00:00:00 2001 From: Viktor Szakats Date: Sat, 10 Oct 2026 19:45:03 +0200 Subject: [PATCH 457/481] test428: skip when running on older MSYS2 versions The root cause for unexplained test428 failures was older MSYS2/Cygwin runtimes filtering out set but empty environment variables when a Cygwin process launches a non-Cygwin one. In curl this happens when runtests launches the native Windows curl tool. MSYS2 3.4.2+ (2022-12-16) no longer does this filtering by default. In CI the affected environments are AppVeyor CI's Visual Studio 2015/2017/2019 images, which use MSYS2 3.0.7 (newer images have 3.6.5 and 3.6.9). Detect this condition in runtests and skip the test when unsupported. Fixing: ``` === Start of file stderr428 curl: Variable 'BLANK' import fail, not set curl: log/17/cmd:3 config file option '--variable' variable expansion failure curl: option -K: variable expansion failure curl: try 'curl --help' or 'curl --manual' for more information === End of file stderr428 ``` Refs: ``` 2022-09-24 - Changed behavior for empty env vars Empty environment variables are no longer removed when starting a new non-cygwin process. ``` https://www.msys2.org/news/#2022-09-24-changed-behavior-for-empty-env-vars https://github.com/msys2/msys2-runtime/commit/59b6d44bc001b07793310a885424c203a8ff2cfb https://github.com/msys2/msys2-runtime/pull/125 Notes: - I did my best to figure out the cutoff version, but could only actually test that 3.6.5+ and 3.0.7. Please adjust if inaccurate. - MSYS2 allows setting `MSYS=noemptyenvvalues` to disable the new behavior and breaking test428. This patch does not address this exception. - Cygwin and MSYS2 are considered interchangable in the codebase, this patch included. However, AFAICT Cygwin did not receive this particular fix as of today's Cygwin master. Let us know if this causes an issue. Follow-up to 0774051964143898e167d12be851139f9a49c622 #17991 Follow-up to 7cf8414fabc3063cc3d2121eacec4a6daa4164a8 Cherry-picked from #23306 Closes #23345 --- tests/data/test428 | 3 +++ tests/runtests.pl | 6 ++++++ 2 files changed, 9 insertions(+) diff --git a/tests/data/test428 b/tests/data/test428 index 73ef94f4c4e3..306097ff853d 100644 --- a/tests/data/test428 +++ b/tests/data/test428 @@ -36,6 +36,9 @@ FUNVALUE=contents VALUE2=curl BLANK= + +blank-env + Expand environment variables within config file diff --git a/tests/runtests.pl b/tests/runtests.pl index 76c0b180cd07..901c8b047f2b 100755 --- a/tests/runtests.pl +++ b/tests/runtests.pl @@ -130,6 +130,7 @@ BEGIN my $uname_release = qx(uname -r); my $is_wsl = $uname_release =~ /Microsoft$/; +my $uname_release_ver = $uname_release =~ /^(\d+)\.(\d+)\.(\d+)/ ? $1 * 10000 + $2 * 100 + $3 : 0; my $http_ipv6; # set if HTTP server has IPv6 support my $http_unix; # set if HTTP server has Unix sockets support @@ -569,6 +570,11 @@ sub checksystemfeatures { if($curl =~ /cygwin|msys/i) { $feature{"cygwin"} = 1; } + # Assume set but empty environment variables are supported, except + # by MSYS2 <3.4.2 (2022-12-16) processes starting non-Cygwin ones. + if(($^O ne 'cygwin' && $^O ne 'msys') || $curl =~ /cygwin|msys/i || $uname_release_ver >= 30605) { + $feature{"blank-env"} = 1; + } if($libcurl =~ /\sschannel\b/i) { $feature{"Schannel"} = 1; $feature{"SSLpinning"} = 1; From 49f003f17391d547f7aea3e893308f169ede74ad Mon Sep 17 00:00:00 2001 From: Viktor Szakats Date: Fri, 9 Oct 2026 21:00:15 +0200 Subject: [PATCH 458/481] unit1664: add workaround for VS2019 16.3 - VS2022 17.4 x64 miscompile It affects `_MSC_VER >= 1923 && _MSC_VER <= 1934`, combined with `-O1` or `-O2`, and inlining reduced via `-Ob0` or `-Ob1` (CMake RelWithDebInfo config). Also with `-O2 -Ob2` (CMake Release config), in curl `units.exe` context. Introduced in: Visual Studio 2019 v16.3 (~2019-09-23) Fixed in: Visual Studio 2022 v17.5 (~2023-02-21) Godbolt reproducer: https://godbolt.org/z/T78GWxzeT As seen in AppVeyor CI: - CM VS2019, Debug , x64, OpenSSL 3.0 + Schannel, Shared, !verbose, Build-tests: PASS - CM VS2019, Release, x64, OpenSSL 3.0 + Schannel, Shared, !verbose, Build-tests: FAIL test1664 - CM VS2019, Release, x86, OpenSSL 3.0 + Schannel, Shared, !verbose, Build-tests: PASS - VS2017/VS2022, Debug/Release: PASS Fixing: ``` # Visual Studio Community 2019 v16.11.50 -- Building for: Visual Studio 16 2019 -- Using CMake version 4.1.0 -- Selecting Windows SDK version 10.0.26100.0 to target Windows 10.0.17763. -- The C compiler identification is MSVC 19.29.30159.0 -- Check for working C compiler: C:/Program Files (x86)/Microsoft Visual Studio/2019/Community/VC/Tools/MSVC/14.29.30133/bin/Hostx64/x64/cl.exe - skipped -- CMake platform flags: WIN32 MSVC-1929 [...] -- Found _WIN32_WINNT=0x0a00 -- Picky compiler options: -W4 -Wall -wd4061 -wd4191 -wd4255 -wd4464 -wd4548 -wd4574 -wd4668 -wd4710 -wd4711 -wd4746 -wd4820 -wd5045 [...] test 1664 [unit tests for strparse.c string parsing functions] 1664: stdout FAILED: --- log/check-expected +++ log/check-generated @@ -154,9 +154,9 @@ 3: (%62) 6, line 0[LF] 4: (%5c) 6, line 0[LF] 5: (%20) 6, line 0[LF] -6: (%0a) 0, line 1[LF] -7: (%0d) 0, line 1[LF] -8: (%0d) 0, line 1[LF] +6: (%00) 0, line 1[LF] +7: (%00) 0, line 1[LF] +8: (%0a) 0, line 1[LF] 9: (%0c) 6, line 0[LF] 10: (%00) 6, line 0[LF] curlx_str_hex[LF] [...] TESTFAIL: These test cases failed: 1664 ``` Ref: https://ci.appveyor.com/project/curlorg/curl/builds/54864988/job/m823tifoodo7tt1a Bug: https://github.com/curl/curl/pull/23306#issuecomment-6043152120 Bug: https://github.com/curl/curl/pull/23306#issuecomment-6047833481 Bug: https://github.com/curl/curl/pull/23306#issuecomment-6051687170 Ref: https://github.com/curl/curl/pull/23347 (seeking a minimal reproducer) Cherry-picked from #23306 Closes #23337 --- tests/unit/unit1664.c | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/tests/unit/unit1664.c b/tests/unit/unit1664.c index f4574d485c67..86fbbc191e98 100644 --- a/tests/unit/unit1664.c +++ b/tests/unit/unit1664.c @@ -367,7 +367,16 @@ static CURLcode test_unit1664(const char *arg) const char *orgline = line; int rc = curlx_str_newline(&line); curl_mprintf("%d: (%%%02x) %d, line %d\n", - i, (unsigned int)*orgline, rc, (int)(line - orgline)); +/* Workaround for the expression erroneously returning the byte from the next + address for inputs \n, \r and \r\n. Thus returning NUL, NUL, \n instead of + the expected \n, \r, \r. */ +#if defined(_MSC_VER) && _MSC_VER >= 1923 && _MSC_VER <= 1934 && \ + defined(_WIN64) + i, (unsigned int)newl[i][0], +#else + i, (unsigned int)*orgline, +#endif + rc, (int)(line - orgline)); } } From 9b8e5442a65b935c08b97b1b9ddb9a82180e2f2c Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 11 Oct 2026 17:43:33 +0000 Subject: [PATCH 459/481] GHA: update dependency pyspelling to v2.13 Closes #23364 --- .github/scripts/requirements-docs.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/scripts/requirements-docs.txt b/.github/scripts/requirements-docs.txt index 6850461de7bc..6379c6ef352c 100644 --- a/.github/scripts/requirements-docs.txt +++ b/.github/scripts/requirements-docs.txt @@ -2,4 +2,4 @@ # # SPDX-License-Identifier: curl -pyspelling==2.12.1 +pyspelling==2.13 From e197647373e1e9210b60e81e84296633a802441c Mon Sep 17 00:00:00 2001 From: Viktor Szakats Date: Sun, 11 Oct 2026 15:18:36 +0200 Subject: [PATCH 460/481] test1664: delete duplicate sub-test Closes #23362 --- tests/data/test1664 | 8 -------- tests/unit/unit1664.c | 21 --------------------- 2 files changed, 29 deletions(-) diff --git a/tests/data/test1664 b/tests/data/test1664 index ccb0579be775..a8f2bffb14f8 100644 --- a/tests/data/test1664 +++ b/tests/data/test1664 @@ -77,14 +77,6 @@ curlx_str_singlespace 7: (" ") 5, line 0 8: ("") 5, line 0 -curlx_str_single -0: ("a") 0, line 1 -1: ("aa") 0, line 1 -2: ("A") 5, line 0 -3: ("b") 5, line 0 -4: ("\") 5, line 0 -5: (" ") 5, line 0 -6: ("") 5, line 0 curlx_str_number 0: ("1") 0, [1] line 1 1: ("10000") 7, [0] line 0 diff --git a/tests/unit/unit1664.c b/tests/unit/unit1664.c index 86fbbc191e98..8fc58a37f3c6 100644 --- a/tests/unit/unit1664.c +++ b/tests/unit/unit1664.c @@ -155,27 +155,6 @@ static CURLcode test_unit1664(const char *arg) } } - { - static const char * const single[] = { - "a", - "aa", - "A", - "b", - "\\", - " ", - "", - NULL - }; - curl_mprintf("curlx_str_single\n"); - for(i = 0; single[i]; i++) { - const char *line = single[i]; - const char *orgline = line; - int rc = curlx_str_single(&line, 'a'); - curl_mprintf("%d: (\"%s\") %d, line %d\n", - i, orgline, rc, (int)(line - orgline)); - } - } - { static const char * const nums[] = { "1", From 70c084a0123b468fe7078e3fc72c78e1b66394b6 Mon Sep 17 00:00:00 2001 From: Viktor Szakats Date: Thu, 8 Oct 2026 03:15:12 +0200 Subject: [PATCH 461/481] docs: note CAINFO/SSL_VERIFYPEER/SSL_VERIFYSTATUS effect on LDAP Say how these TLS options behave when using mainline OpenLDAP, Apple's LDAP fork and WinLDAP. Also: - remove mention of `USE_OPENLDAP` internal C macro. - sync `CURLOPT_PINNEDPUBLICKEY` man page with the above. Follow-up to f2692b54f74b8bb6058ecd3cf4abcc96e8ab36ba #21682 For reference about the two LDAP "tracks" curl supports: - lib/ldap.c: "legacy" LDAP, WinLDAP, Apple's OpenLDAP fork (existing releases reported as `OpenLDAP/2.4.28/Apple`, fixed version), IBMLDAP*, use their own TLS-backend, some curl TLS options explicitly supported build-time-macro/runtime-depending, `!USE_OPENLDAP` *: there is almost nothing publicly known about IBMLDAP. - lib/openssl.c: `USE_OPENLDAP`, mainline OpenLDAP, versioned, uses curl's TLS-backend, reports itself as `OpenLDAP/x.y.z`. Reported-by: Joshua Rogers Closes #23303 --- docs/libcurl/opts/CURLOPT_CAINFO.md | 4 ++++ docs/libcurl/opts/CURLOPT_PINNEDPUBLICKEY.md | 7 +++---- docs/libcurl/opts/CURLOPT_SSL_VERIFYPEER.md | 4 ++++ docs/libcurl/opts/CURLOPT_SSL_VERIFYSTATUS.md | 4 ++++ 4 files changed, 15 insertions(+), 4 deletions(-) diff --git a/docs/libcurl/opts/CURLOPT_CAINFO.md b/docs/libcurl/opts/CURLOPT_CAINFO.md index 86183677284d..999c9c87ee1c 100644 --- a/docs/libcurl/opts/CURLOPT_CAINFO.md +++ b/docs/libcurl/opts/CURLOPT_CAINFO.md @@ -55,6 +55,10 @@ previous ones. Set it to NULL to disable its use again. The default value for this can be figured out with CURLINFO_CAINFO(3). +For LDAP, this option only has an effect when using the OpenLDAP backend, +which routes TLS through libcurl. It is not supported by Apple's OpenLDAP +fork. WinLDAP uses the Windows CA store for root certificates. + # DEFAULT Built-in system specific. When curl is built with Schannel, this option is not diff --git a/docs/libcurl/opts/CURLOPT_PINNEDPUBLICKEY.md b/docs/libcurl/opts/CURLOPT_PINNEDPUBLICKEY.md index ed4011d57fa3..0f2a7583c048 100644 --- a/docs/libcurl/opts/CURLOPT_PINNEDPUBLICKEY.md +++ b/docs/libcurl/opts/CURLOPT_PINNEDPUBLICKEY.md @@ -57,10 +57,9 @@ The pinned public key is used to verify the initial origin used in a transfer. If the transfer is set to follow redirects to other origins, they are *not* checked against this key. -This option has no effect on LDAP connections when libcurl uses the legacy LDAP -backend. That backend manages TLS independently of curl's TLS layer. When -libcurl is built with USE_OPENLDAP, the OpenLDAP backend routes TLS through -curl's layer and this option is honored. +For LDAP, this option only has an effect when using the OpenLDAP backend, +which routes TLS through libcurl. It is not supported by Apple's OpenLDAP +fork. # DEFAULT diff --git a/docs/libcurl/opts/CURLOPT_SSL_VERIFYPEER.md b/docs/libcurl/opts/CURLOPT_SSL_VERIFYPEER.md index ad0242ba5860..d77b499db59c 100644 --- a/docs/libcurl/opts/CURLOPT_SSL_VERIFYPEER.md +++ b/docs/libcurl/opts/CURLOPT_SSL_VERIFYPEER.md @@ -69,6 +69,10 @@ HSTS and Alt-Svc information to be stored and used subsequently. Disabling certificate verification can make libcurl trust and use such information from malicious servers. +For LDAP, this option is supported when using the OpenLDAP backend, and with +the WinLDAP backend since 8.22.0. With Apple's OpenLDAP fork, the certificate +chain is still verified when this option is disabled. + # DEFAULT 1 - enabled diff --git a/docs/libcurl/opts/CURLOPT_SSL_VERIFYSTATUS.md b/docs/libcurl/opts/CURLOPT_SSL_VERIFYSTATUS.md index b1d373eef69c..86557298882c 100644 --- a/docs/libcurl/opts/CURLOPT_SSL_VERIFYSTATUS.md +++ b/docs/libcurl/opts/CURLOPT_SSL_VERIFYSTATUS.md @@ -38,6 +38,10 @@ using the "Certificate Status Request" TLS extension (aka. OCSP stapling). Note that if this option is enabled but the server does not support the TLS extension, the verification fails. +For LDAP, this option only has an effect when using the OpenLDAP backend, +which routes TLS through libcurl. It is not supported by Apple's OpenLDAP +fork. + # DEFAULT 0 From 6cd9bb6eebf70cffb8a2e9b72023a57fbebe2a0b Mon Sep 17 00:00:00 2001 From: Daniel Stenberg Date: Mon, 12 Oct 2026 07:52:38 +0200 Subject: [PATCH 462/481] VERSIONS/RELEASE-PROCEDURE: adjusted release details --- docs/RELEASE-PROCEDURE.md | 13 +++++++------ docs/VERSIONS.md | 3 ++- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/docs/RELEASE-PROCEDURE.md b/docs/RELEASE-PROCEDURE.md index 81dcbc628455..38ee29d3d802 100644 --- a/docs/RELEASE-PROCEDURE.md +++ b/docs/RELEASE-PROCEDURE.md @@ -125,9 +125,10 @@ push for it. Based on the description above, here are some planned future release dates: -- June 24, 2026 -- September 2, 2026 -- October 28, 2026 -- December 23, 2026 -- February 17, 2027 -- April 14, 2027 +- October 14, 2026 +- December 9, 2026 +- February 3, 2027 +- March 31, 2027 +- May 26, 2027 +- July 21, 2027 +- September 15, 2027 diff --git a/docs/VERSIONS.md b/docs/VERSIONS.md index 8a8de04392da..84f4077dda35 100644 --- a/docs/VERSIONS.md +++ b/docs/VERSIONS.md @@ -68,7 +68,8 @@ dates. The tool was called `httpget` before 2.0, `urlget` before 4.0 then `curl` since 4.0. `libcurl` and `curl` are always released in sync, using the same version numbers. -- 8.23.0: pending +- 8.24.0: pending +- 8.23.0: October 14, 2026 - 8.22.0: September 2, 2026 - 8.21.0: June 24 2026 - 8.20.0: April 29 2026 From 8b0744af5b1b631da5b938ca31c3f9895ad5a321 Mon Sep 17 00:00:00 2001 From: Stefan Eissing Date: Mon, 12 Oct 2026 07:40:45 +0200 Subject: [PATCH 463/481] multi: adjust the cleanup order in multi_done Closes #23370 --- lib/multi.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/multi.c b/lib/multi.c index e7e7811adf1e..8e2d1fd5cf42 100644 --- a/lib/multi.c +++ b/lib/multi.c @@ -729,12 +729,6 @@ static CURLcode multi_done(struct Curl_easy *data, /* Make sure that transfer client writes are really done now. */ result = Curl_1st_fatal(result, Curl_xfer_write_done(data, premature)); - /* Inform connection filters that this transfer is done */ - if(conn) - Curl_conn_ev_data_done(data, premature); - - multi_schedule_pending(data->multi); /* connection / multiplex */ - if(!result) result = Curl_req_done(&data->req, data, premature); @@ -742,12 +736,18 @@ static CURLcode multi_done(struct Curl_easy *data, /* Detach connection and decide what to do with it. */ struct multi_done_ctx mdctx; + /* Inform connection filters that this transfer is done and they + * need to give up any references to `data`. */ + Curl_conn_ev_data_done(data, premature); Curl_detach_connection(data); Curl_dnscache_prune(data, pnow); memset(&mdctx, 0, sizeof(mdctx)); mdctx.premature = premature; Curl_cpool_return(data, conn, multi_done_locked, &mdctx, pnow); + + if(data->multi) + multi_schedule_pending(data->multi); } /* flush the netrc cache */ From 5e727ca855429a684614a24646cb06fd965a1f7d Mon Sep 17 00:00:00 2001 From: Stefan Eissing Date: Thu, 8 Oct 2026 15:32:28 +0200 Subject: [PATCH 464/481] GnuTLS: remove our own OCSP signature verification GnuTLS documents for gnutls_certificate_verify_peers2() that it already takes a received OCSP response into account. Therefore our own code needs only to check if a stapled OCSP response was receieved when verifystatus is requested. Besides being superfluous, the code was also producing false negatives as the list of trust anchors used is insufficent for verification, as correclty pointed out in #23311. Closes #23312 --- lib/vtls/gtls.c | 117 ++++-------------------------------------------- 1 file changed, 8 insertions(+), 109 deletions(-) diff --git a/lib/vtls/gtls.c b/lib/vtls/gtls.c index 25f49b81d0c7..7955dbadb670 100644 --- a/lib/vtls/gtls.c +++ b/lib/vtls/gtls.c @@ -1430,127 +1430,26 @@ static void gtls_infof_cert(struct Curl_easy *data, static CURLcode gtls_verify_ocsp_status(struct Curl_easy *data, gnutls_session_t session) { - gnutls_ocsp_resp_t ocsp_resp = NULL; gnutls_datum_t status_request; - gnutls_certificate_credentials_t creds = NULL; - gnutls_x509_trust_list_t tlist = NULL; - unsigned int verify_status = 0; - gnutls_ocsp_cert_status_t status = GNUTLS_OCSP_CERT_UNKNOWN; - gnutls_x509_crl_reason_t reason; - CURLcode result = CURLE_OK; int rc; + /* We use gnutls_certificate_verify_peers2() to verify the result + * which - according to GnuTLS documentation - already takes + * an OCSP stapling extension received into account. + * We therefore need only to check if we indeed received + * a stapled OCSP response here. */ rc = gnutls_ocsp_status_request_get(session, &status_request); if(rc == GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE) { failf(data, "No OCSP response received"); - result = CURLE_SSL_INVALIDCERTSTATUS; - goto out; + return CURLE_SSL_INVALIDCERTSTATUS; } else if(rc < 0) { failf(data, "Invalid OCSP response received"); - result = CURLE_SSL_INVALIDCERTSTATUS; - goto out; - } - - rc = gnutls_ocsp_resp_init(&ocsp_resp); - if(rc < 0) { - failf(data, "Failed to initialize OCSP response object"); - result = CURLE_SSL_INVALIDCERTSTATUS; - goto out; - } - - rc = gnutls_ocsp_resp_import(ocsp_resp, &status_request); - if(rc < 0) { - failf(data, "Invalid OCSP response received"); - result = CURLE_SSL_INVALIDCERTSTATUS; - goto out; + return CURLE_SSL_INVALIDCERTSTATUS; } - if(!gnutls_credentials_get(session, GNUTLS_CRD_CERTIFICATE, (void **)&creds)) - gnutls_certificate_get_trust_list(creds, &tlist); - if(!tlist) { - failf(data, "OCSP response signature verification failed"); - result = CURLE_SSL_INVALIDCERTSTATUS; - goto out; - } - rc = gnutls_ocsp_resp_verify(ocsp_resp, tlist, &verify_status, 0); - if(rc < 0 || verify_status) { - failf(data, "OCSP response signature verification failed"); - result = CURLE_SSL_INVALIDCERTSTATUS; - goto out; - } - - (void)gnutls_ocsp_resp_get_single(ocsp_resp, 0, NULL, NULL, NULL, NULL, - &status, NULL, NULL, NULL, &reason); - - switch(status) { - case GNUTLS_OCSP_CERT_GOOD: - break; - - case GNUTLS_OCSP_CERT_REVOKED: { - const char *crl_reason; - - switch(reason) { - default: - case GNUTLS_X509_CRLREASON_UNSPECIFIED: - crl_reason = "unspecified reason"; - break; - - case GNUTLS_X509_CRLREASON_KEYCOMPROMISE: - crl_reason = "private key compromised"; - break; - - case GNUTLS_X509_CRLREASON_CACOMPROMISE: - crl_reason = "CA compromised"; - break; - - case GNUTLS_X509_CRLREASON_AFFILIATIONCHANGED: - crl_reason = "affiliation has changed"; - break; - - case GNUTLS_X509_CRLREASON_SUPERSEDED: - crl_reason = "certificate superseded"; - break; - - case GNUTLS_X509_CRLREASON_CESSATIONOFOPERATION: - crl_reason = "operation has ceased"; - break; - - case GNUTLS_X509_CRLREASON_CERTIFICATEHOLD: - crl_reason = "certificate is on hold"; - break; - - case GNUTLS_X509_CRLREASON_REMOVEFROMCRL: - crl_reason = "will be removed from delta CRL"; - break; - - case GNUTLS_X509_CRLREASON_PRIVILEGEWITHDRAWN: - crl_reason = "privilege withdrawn"; - break; - - case GNUTLS_X509_CRLREASON_AACOMPROMISE: - crl_reason = "AA compromised"; - break; - } - - failf(data, "Server certificate was revoked: %s", crl_reason); - break; - } - - default: - case GNUTLS_OCSP_CERT_UNKNOWN: - failf(data, "Server certificate status is unknown"); - break; - } - - result = (status != GNUTLS_OCSP_CERT_GOOD) ? - CURLE_SSL_INVALIDCERTSTATUS : CURLE_OK; - -out: - if(ocsp_resp) - gnutls_ocsp_resp_deinit(ocsp_resp); - return result; + return CURLE_OK; } struct gtls_cert_chain { From 3c60b05098289a295efe97f6aa8d296b62b70f95 Mon Sep 17 00:00:00 2001 From: jdymitarai Date: Mon, 12 Oct 2026 00:08:22 +0900 Subject: [PATCH 465/481] openssl: support X509 store caching when CApath is configured Closes #23363 --- lib/vtls/openssl.c | 35 ++++++++++++++++++++++++++++++----- 1 file changed, 30 insertions(+), 5 deletions(-) diff --git a/lib/vtls/openssl.c b/lib/vtls/openssl.c index 27374e589e6e..7bb7a8c50e2e 100644 --- a/lib/vtls/openssl.c +++ b/lib/vtls/openssl.c @@ -3167,6 +3167,7 @@ static CURLcode ossl_populate_x509_store(struct Curl_cfilter *cf, struct ossl_x509_share { char *CAfile; /* CAfile path used to generate X509 store */ + char *CApath; /* CApath path used to generate X509 store */ X509_STORE *store; /* cached X509 store or NULL if none */ struct curltime time; /* when the cached store was created */ BIT(store_is_empty); /* no certs/paths/blobs are in the store */ @@ -3184,6 +3185,7 @@ static void oss_x509_share_free(const void *key, size_t key_len, void *p) X509_STORE_free(share->store); } curlx_free(share->CAfile); + curlx_free(share->CApath); curlx_free(share); } @@ -3208,9 +3210,21 @@ static bool ossl_cached_x509_store_different(struct Curl_cfilter *cf, (void)data; if(mb->no_partialchain != conn_config->no_partialchain) return TRUE; - if(!mb->CAfile || !conn_config->CAfile) - return mb->CAfile != conn_config->CAfile; - return strcmp(mb->CAfile, conn_config->CAfile); + if(!mb->CAfile || !conn_config->CAfile) { + if(mb->CAfile != conn_config->CAfile) + return TRUE; + } + else if(strcmp(mb->CAfile, conn_config->CAfile)) + return TRUE; + + if(!mb->CApath || !conn_config->CApath) { + if(mb->CApath != conn_config->CApath) + return TRUE; + } + else if(strcmp(mb->CApath, conn_config->CApath)) + return TRUE; + + return FALSE; } static X509_STORE *ossl_get_cached_x509_store(struct Curl_cfilter *cf, @@ -3267,6 +3281,7 @@ static void ossl_set_cached_x509_store(struct Curl_cfilter *cf, if(X509_STORE_up_ref(store)) { char *CAfile = NULL; + char *CApath = NULL; if(conn_config->CAfile) { CAfile = curlx_strdup(conn_config->CAfile); @@ -3276,15 +3291,26 @@ static void ossl_set_cached_x509_store(struct Curl_cfilter *cf, } } + if(conn_config->CApath) { + CApath = curlx_strdup(conn_config->CApath); + if(!CApath) { + curlx_free(CAfile); + X509_STORE_free(store); + return; + } + } + if(share->store) { X509_STORE_free(share->store); curlx_free(share->CAfile); + curlx_free(share->CApath); } share->time = *Curl_pgrs_now(data); share->store = store; share->store_is_empty = is_empty; share->CAfile = CAfile; + share->CApath = CApath; share->no_partialchain = conn_config->no_partialchain; } } @@ -3298,12 +3324,11 @@ CURLcode Curl_ssl_setup_x509_store(struct Curl_cfilter *cf, X509_STORE *cached_store; bool cache_criteria_met, is_empty; - /* Consider the X509 store cacheable if it comes exclusively from a CAfile, + /* Consider the X509 store cacheable if it comes from CAfile/CApath, or no source is provided and we are falling back to OpenSSL's built-in default. */ cache_criteria_met = (data->set.ssl_ca_cache_timeout != 0) && conn_config->verifypeer && - !conn_config->CApath && !conn_config->ca_info_blob && !conn_config->CRLfile && !conn_config->native_ca_store; From 1aac64a26de0cc0bafd9e502d899a4fbc41dc82f Mon Sep 17 00:00:00 2001 From: Viktor Szakats Date: Mon, 12 Oct 2026 15:19:33 +0200 Subject: [PATCH 466/481] test1119, 1167: disable MSYS2 argument conversion narrowly Narrow the exclusion from `*` to `/D`, to fix certain build combinations relying on the heuristics, but still fix the narrower case that we saw breaking when using MSVC with CMake's Visual Studio generator, with `/DINW32 /D_WINDOWS` passed to the compiler, which hit the heuristics by default. Tested via #23306: https://ci.appveyor.com/project/curlorg/curl/builds/54881098 Ref: https://curl.se/dev/log.cgi?id=20261010032513-1037281 Reported-by: Marcel Raad Fixes #23372 Follow-up to 2f3f99da36f7cddbbf44939f992b9e3da6b2a75f #23339 Closes #23375 --- tests/test1119.pl | 2 +- tests/test1167.pl | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test1119.pl b/tests/test1119.pl index d77cd2c8a0d7..ae5d30621d43 100755 --- a/tests/test1119.pl +++ b/tests/test1119.pl @@ -66,7 +66,7 @@ # included by it, which *should* be all headers sub scanenum { my ($file) = @_; - local $ENV{'MSYS2_ARG_CONV_EXCL'} = '*'; + local $ENV{'MSYS2_ARG_CONV_EXCL'} = '/D'; open(my $h_in, "-|", "$Cpreprocessor $i$file") or die "Cannot preprocess $file"; while(<$h_in>) { if(/enum\s+(\S+\s+)?{/ .. /}/) { diff --git a/tests/test1167.pl b/tests/test1167.pl index c3c87e6924c3..db4fa1c37318 100755 --- a/tests/test1167.pl +++ b/tests/test1167.pl @@ -71,7 +71,7 @@ sub scanenums { my ($file) = @_; my $skipit = 0; - local $ENV{'MSYS2_ARG_CONV_EXCL'} = '*'; + local $ENV{'MSYS2_ARG_CONV_EXCL'} = '/D'; open(H_IN, "-|", "$Cpreprocessor -DCURL_DISABLE_DEPRECATION $i$file") or die "Cannot preprocess $file"; From 4f9edc8e6f19dfd243e40ebf2780f9a15c4bab79 Mon Sep 17 00:00:00 2001 From: Viktor Szakats Date: Mon, 12 Oct 2026 15:30:08 +0200 Subject: [PATCH 467/481] testcurl.pl: drop support for building c-ares from embedded source tree Closes #23374 --- tests/testcurl.pl | 101 +--------------------------------------------- 1 file changed, 1 insertion(+), 100 deletions(-) diff --git a/tests/testcurl.pl b/tests/testcurl.pl index 0c7068a0eedc..5e9e1de5fdad 100755 --- a/tests/testcurl.pl +++ b/tests/testcurl.pl @@ -152,7 +152,7 @@ # Do the platform-specific stuff here $confheader = 'curl_config.h'; $binext = ''; -$libext = '.la'; # .la since both libcurl and libcares are made with libtool +$libext = '.la'; # .la since libcurl is made with libtool if($^O eq 'MSWin32' || $targetos) { if(!$targetos) { # If no target defined on Windows, let's assume vc @@ -354,20 +354,14 @@ sub get_host_triplet { # off that path from all possible logs and error messages etc. $pwd = getcwd(); -my $have_embedded_ares = 0; - if(-d $CURLDIR) { if($git && -d "$CURLDIR/.git") { logit "$CURLDIR is verified to be a fine git source dir"; # remove the generated sources to force them to be re-generated each # time we run this test unlink "$CURLDIR/src/tool_hugehelp.c"; - # find out if curl source directory has an in-tree c-ares repo - $have_embedded_ares = 1 if(-f "$CURLDIR/ares/GIT-INFO"); } elsif(!$git && -f "$CURLDIR/tests/testcurl.pl") { logit "$CURLDIR is verified to be a fine daily source dir"; - # find out if curl source directory has an in-tree c-ares extracted tarball - $have_embedded_ares = 1 if(-f "$CURLDIR/ares/ares_build.h"); } else { mydie "$CURLDIR is not a daily source directory or checked out from git!" } @@ -425,32 +419,6 @@ sub get_host_triplet { logit " $_"; } - if(-d "ares/.git") { - chdir "ares"; - - if($nogitpull) { - logit "skipping git pull (--nogitpull) in ares"; - } else { - logit "run git pull in ares"; - system("git pull 2>&1"); - $gitstat += $?; - logit "failed to update from ares git ($?), continue anyway" if($?); - - # Set timestamp to the UTC the git update took place. - $timestamp = scalar(gmtime)." UTC" if(!$gitstat); - } - - # get the last 5 commits for show (even if no pull was made) - @commits = qx(git log --pretty=oneline --abbrev-commit -5); - logit "The most recent ares git commits:"; - for(@commits) { - chomp ($_); - logit " $_"; - } - - chdir $CURLDIR; - } - if($nobuildconf) { logit "told to not run autoreconf -fi"; } @@ -581,73 +549,6 @@ sub findinpath { } close($f); -if(($have_embedded_ares) && - (grepfile("^#define USE_ARES", "lib/$confheader"))) { - print "\n"; - logit "setup to build ares"; - - if(-f "./ares/libcares.pc") { - logit_spaced "display ares/libcares.pc"; - if(open($f, "<", "ares/libcares.pc")) { - while(<$f>) { - my $ll = $_; - print $ll if(($ll !~ /^ *#/) && ($ll !~ /^ *$/)); - } - close($f); - } - } - - if(-f "./ares/ares_build.h") { - logit_spaced "display ares/ares_build.h"; - if(open($f, "<", "ares/ares_build.h")) { - while(<$f>) { - my $ll = $_; - print $ll if(($ll =~ /^ *# *define *CARES_/) && ($ll !~ /__CARES_BUILD_H/)); - } - close($f); - } - } - else { - mydie "no ares_build.h created/found"; - } - - $confheader =~ s/curl/ares/; - logit_spaced "display ares/$confheader"; - if(open($f, "<", "ares/$confheader")) { - while(<$f>) { - print if /^ *#/; - } - close($f); - } - - print "\n"; - logit "build ares"; - chdir "ares"; - - if($targetos && !$configurebuild) { - logit "$make -f Makefile.$targetos"; - open($f, "-|", "$make -f Makefile.$targetos 2>&1") or die; - } - else { - logit $make; - open($f, "-|", "$make 2>&1") or die; - } - while(<$f>) { - s/$pwd//g; - print; - } - close($f); - - if(-f "libcares$libext") { - logit "ares is now built successfully (libcares$libext)"; - } else { - mydie "ares build failed (libcares$libext)"; - } - - # cd back to the curl build dir - chdir "$pwd/$build"; -} - my $mkcmd = "$make -i" . ($targetos && !$configurebuild ? " $targetos" : ""); logit $mkcmd; open($f, "-|", "$mkcmd 2>&1") or die; From 8cdee8a18e46c3a01f871dd92dfb64c3ae605eec Mon Sep 17 00:00:00 2001 From: Viktor Szakats Date: Mon, 12 Oct 2026 22:05:11 +0200 Subject: [PATCH 468/481] lib557: exclude three floating point test cases for VS2013 and earlier curl's printf relies on the CRT to print floating point numbers. The old (pre-UCRT) CRT used by VS2013 and earlier toolchains behave differently than expected. Exclude them to make the test pass with these toolchains. Also: - output double conversion results to stdout to make it appear in CI logs. Test failures as seen with VS2013: ``` test 0557 [curl_mprintf() testing] 557: stdout FAILED: --- log/1/check-expected +++ log/1/check-generated @@ -6,7 +6,12 @@ All curl_mprintf() signed long tests OK![LF] All curl_mprintf() curl_off_t tests OK![LF] All curl_mprintf() strings tests OK![LF] -All float strings tests OK![LF] +sprintf line 1117 failed:[LF] +we ''[LF] +system: '9.000000'[LF] +MISMATCH (suffix): %.17Le curl=1.23456789012345670e+000 libc=e+00[LF] +MISMATCH (suffix): %.17LE curl=1.23456789012345670E+000 libc=E+00[LF] +test_float_formatting Failed![LF] All curl_mprintf() octal and hexadecimal tests OK![LF] [...] ``` Ref: https://ci.appveyor.com/project/curlorg/curl/builds/54883622/job/pdynkxfjxtfhlomb#L949 Follow-up to bdda3a268f0fd1a1bb4b16b9322aa6bc055b8e76 #22706 Follow-up to 48322e9f09f2b51f77d37c5e04a3f370bb36bfc4 #22719 Cherry-picked from #23306 Closes #23377 --- tests/libtest/lib557.c | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/tests/libtest/lib557.c b/tests/libtest/lib557.c index 851776183889..24cb1bfd2554 100644 --- a/tests/libtest/lib557.c +++ b/tests/libtest/lib557.c @@ -1071,10 +1071,13 @@ static int double_check(void) struct dbcheck c[] = { { "%.17Lf", /* "1.23456789012345669" */ "1.23456789012345", "", }, +/* VS2013 and earlier CRT vsnprintf() return slightly different results */ +#if !defined(_MSC_VER) || (_MSC_VER >= 1900) { "%.17Le", /* "1.23456789012345669e+00" */ "1.234567890123456", "e+00" }, { "%.17LE", /* "1.23456789012345669E+00" */ "1.234567890123456", "E+00" }, +#endif { "%.17Lg", /* "1.2345678901234567" */ "1.234567890123456", "" }, { "%.17LG", /* "1.2345678901234567" */ @@ -1085,21 +1088,19 @@ static int double_check(void) size_t len = curl_msnprintf(curl_out, sizeof(curl_out), c[i].fmt, val); if(strncmp(curl_out, c[i].prefix, strlen(c[i].prefix))) { - curl_mfprintf(stderr, - "MISMATCH (prefix): %s curl=%s libc=%s\n", - c[i].fmt, curl_out, c[i].prefix); + curl_mprintf("MISMATCH (prefix): %s curl=%s libc=%s\n", + c[i].fmt, curl_out, c[i].prefix); mismatches++; } if((len < strlen(c[i].suffix) || strncmp(curl_out + len - strlen(c[i].suffix), c[i].suffix, strlen(c[i].suffix)))) { - curl_mfprintf(stderr, - "MISMATCH (suffix): %s curl=%s libc=%s\n", - c[i].fmt, curl_out, c[i].suffix); + curl_mprintf("MISMATCH (suffix): %s curl=%s libc=%s\n", + c[i].fmt, curl_out, c[i].suffix); mismatches++; } } - curl_mfprintf(stderr, "mismatches=%d/5\n", mismatches); + curl_mfprintf(stderr, "mismatches=%d/%zu\n", mismatches, CURL_ARRAYSIZE(c)); return mismatches; } @@ -1113,8 +1114,11 @@ static int test_float_formatting(void) curl_msnprintf(buf, sizeof(buf), "%f", 9.0); errors += string_check(buf, "9.000000"); +/* VS2013 and earlier CRT vsnprintf() does not support this mask */ +#if !defined(_MSC_VER) || (_MSC_VER >= 1900) curl_msnprintf(buf, sizeof(buf), "%F", 9.0); errors += string_check(buf, "9.000000"); +#endif curl_msnprintf(buf, sizeof(buf), "%.1f", 9.1); errors += string_check(buf, "9.1"); From ec79b795bb7c4335fd2d9e0089f9fd134279395b Mon Sep 17 00:00:00 2001 From: Samuel Henrique Date: Mon, 12 Oct 2026 22:28:52 -0700 Subject: [PATCH 469/481] wcurl: import v2026.10.12 Closes #23382 --- docs/wcurl.md | 15 ++++++++++++++- scripts/wcurl | 14 ++++++++++---- 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/docs/wcurl.md b/docs/wcurl.md index 4137e98b35a6..59f426796918 100644 --- a/docs/wcurl.md +++ b/docs/wcurl.md @@ -69,7 +69,7 @@ By default, **wcurl** does: ## * Percent-decode the resulting filename; ## * Use 'index.html' as the default filename - if there is none in the URL. + if there is none in the URL, or if it is '-'. # OPTIONS @@ -107,6 +107,19 @@ Print help message. Any option supported by curl can be set here. This is not used by **wcurl**; it is instead forwarded to the curl invocation. +The value is split on whitespace (spaces, tabs and newlines) and each resulting +word is passed to curl as a separate argument. Quotes and backslashes are not +interpreted, so an argument containing whitespace cannot be passed this way. For +example, **--curl-options='-H "Cookie: a=1; b=2"'** passes the four arguments +`-H`, `"Cookie:`, `a=1;` and `b=2"` to curl, which treats the extra words as +URLs to download. + +To pass arguments that contain whitespace, write them to a curl config file and +pass it with **--curl-options="--config \"**, or use curl directly. + +Everything in \ becomes part of the curl command line, so never +build it from untrusted input. + # URL URL to be downloaded. Anything that is not a parameter is considered diff --git a/scripts/wcurl b/scripts/wcurl index 5fb68275efb1..d4914ee5aa37 100755 --- a/scripts/wcurl +++ b/scripts/wcurl @@ -29,7 +29,7 @@ # Stop on errors, usage of unset variables and disable glob expansion. set -euf -VERSION="2026.08.30" +VERSION="2026.10.12" PROGRAM_NAME="$(basename "$0")" readonly PROGRAM_NAME @@ -74,7 +74,9 @@ Options: -h, --help: Print this usage message. : Any option supported by curl can be set here. This is not used by wcurl; it is - instead forwarded to the curl invocation. + instead forwarded to the curl invocation. The value is split on whitespace and + quotes are not interpreted; use '--config ' for arguments containing + whitespace. Never build it from untrusted input. : URL to be downloaded. Anything that is not a parameter is considered an URL. Whitespace is percent-encoded and the URL is passed to curl, which @@ -202,13 +204,17 @@ percent_decode() # Print the percent-decoded filename portion of the given URL. get_url_filename() { - # Remove protocol and query string if present. + # Remove protocol, query string and URL fragments if present. hostname_and_path="$(printf %s "${1}" | sed -e 's,^[^/]*//,,' -e 's,[?#].*$,,')" # If what remains contains a slash, there is a path; return it percent-decoded. case "${hostname_and_path}" in # sed to remove everything preceding the last '/', e.g.: "example/something" becomes "something" # sed to also replace '\' with the percent_encoded %5C and ':' with %3A - */*) percent_decode "$(printf %s "${hostname_and_path}" | sed -e 's,^.*/,,' -e 's,\\,%5C,g' -e 's,:,%3A,g')" ;; + */*) + filename="$(percent_decode "$(printf %s "${hostname_and_path}" | sed -e 's,^.*/,,' -e 's,\\,%5C,g' -e 's,:,%3A,g')")" + [ "${filename}" = "-" ] && filename="" + printf %s "${filename}" + ;; esac # No slash means there was only a hostname and no path; return empty string. } From 1a338d1cca5bd190af0a12422daa497fc0bbe231 Mon Sep 17 00:00:00 2001 From: Daniel Stenberg Date: Mon, 12 Oct 2026 22:55:46 +0200 Subject: [PATCH 470/481] examples: showcase more options used https://curl.se/libcurl/c/options-in-examples.html lists libcurl options that have no corresponding examples using them. This fixes some gaps. - aws-sigv4.c: use CURLOPT_AWS_SIGV4 - cacertinmem: document that this is using the old way - cainfo-blob: use CURLOPT_CAINFO_BLOB - clientcert-blob: CURLOPT_SSLCERT_BLOB - conditional: use CURLOPT_TIMEVALUE - cookie.c: use CURLOPT_COOKIE - cookiejar: use CURLOPT_COOKIEJAR - doh: show how to use DoH - ftp-listonly: use CURLOPT_DIRLISTONLY - httpput: use CURLOPT_UPLOAD_BUFFERSIZE - httpsig: do RFC 9421 HTTP Message Signatures - limit-rate: limit download rate - nagle: clear TCP_NODELAY - path-as-is: keep dot-dot sequences - trailer: trailing headers using CURLOPT_TRAILERFUNCTION - usercertinmem: document that this is using the old way Closes #23379 --- docs/examples/Makefile.inc | 13 +++++ docs/examples/aws-sigv4.c | 65 ++++++++++++++++++++++ docs/examples/cacertinmem.c | 4 ++ docs/examples/cainfo-blob.c | 76 ++++++++++++++++++++++++++ docs/examples/clientcert-blob.c | 81 ++++++++++++++++++++++++++++ docs/examples/conditional.c | 70 ++++++++++++++++++++++++ docs/examples/cookie.c | 59 ++++++++++++++++++++ docs/examples/cookiejar.c | 63 ++++++++++++++++++++++ docs/examples/doh.c | 60 +++++++++++++++++++++ docs/examples/ftp-listonly.c | 58 ++++++++++++++++++++ docs/examples/httpput.c | 4 ++ docs/examples/httpsig.c | 78 +++++++++++++++++++++++++++ docs/examples/limit-rate.c | 59 ++++++++++++++++++++ docs/examples/nagle.c | 59 ++++++++++++++++++++ docs/examples/path-as-is.c | 60 +++++++++++++++++++++ docs/examples/trailer.c | 95 +++++++++++++++++++++++++++++++++ docs/examples/usercertinmem.c | 9 ++-- 17 files changed, 908 insertions(+), 5 deletions(-) create mode 100644 docs/examples/aws-sigv4.c create mode 100644 docs/examples/cainfo-blob.c create mode 100644 docs/examples/clientcert-blob.c create mode 100644 docs/examples/conditional.c create mode 100644 docs/examples/cookie.c create mode 100644 docs/examples/cookiejar.c create mode 100644 docs/examples/doh.c create mode 100644 docs/examples/ftp-listonly.c create mode 100644 docs/examples/httpsig.c create mode 100644 docs/examples/limit-rate.c create mode 100644 docs/examples/nagle.c create mode 100644 docs/examples/path-as-is.c create mode 100644 docs/examples/trailer.c diff --git a/docs/examples/Makefile.inc b/docs/examples/Makefile.inc index 8c85fb838232..78f5a1636f7c 100644 --- a/docs/examples/Makefile.inc +++ b/docs/examples/Makefile.inc @@ -29,16 +29,24 @@ check_PROGRAMS = \ address-scope \ altsvc \ anyauthput \ + aws-sigv4 \ block_ip \ + cainfo-blob \ certinfo \ chkspeed \ + clientcert-blob \ + conditional \ connect-to \ + cookie \ + cookiejar \ cookie_interface \ debug \ default-scheme \ + doh \ externalsocket \ fileupload \ ftp-delete \ + ftp-listonly \ ftp-wildcard \ ftpget \ ftpgetinfo \ @@ -65,6 +73,7 @@ check_PROGRAMS = \ httpput \ httpput-postfields \ https \ + httpsig \ imap-append \ imap-authzid \ imap-copy \ @@ -83,6 +92,7 @@ check_PROGRAMS = \ interface \ ipv6 \ keepalive \ + limit-rate \ localport \ log_failed_transfers \ maxconnects \ @@ -93,8 +103,10 @@ check_PROGRAMS = \ multi-legacy \ multi-post \ multi-single \ + nagle \ netrc \ parseurl \ + path-as-is \ persistent \ pop3-authzid \ pop3-dele \ @@ -134,6 +146,7 @@ check_PROGRAMS = \ smtp-vrfy \ sslbackend \ synctime \ + trailer \ unixsocket \ url2file \ urlapi \ diff --git a/docs/examples/aws-sigv4.c b/docs/examples/aws-sigv4.c new file mode 100644 index 000000000000..69b04f3acaf9 --- /dev/null +++ b/docs/examples/aws-sigv4.c @@ -0,0 +1,65 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ +/* + * Get a resource from an S3 service using AWS SigV4 authentication. + * + */ +#include + +#include + +int main(void) +{ + CURL *curl; + CURLcode result = curl_global_init(CURL_GLOBAL_ALL); + if(result != CURLE_OK) + return (int)result; + + curl = curl_easy_init(); + if(curl) { + /* Set the S3 object URL */ + curl_easy_setopt(curl, CURLOPT_URL, + "https://mybucket.s3.us-east-1.amazonaws.example/" + "myphoto.jpg"); + + /* Configure AWS SigV4: provider1:provider2:region:service */ + curl_easy_setopt(curl, CURLOPT_AWS_SIGV4, "aws:amz:us-east-1:s3"); + + /* Supply the AWS Access Key ID and Secret Access Key */ + curl_easy_setopt(curl, CURLOPT_USERPWD, + "AKIAIOSFODNN7EXAMPLE:" + "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"); + + result = curl_easy_perform(curl); + if(result != CURLE_OK) + fprintf(stderr, "curl_easy_perform() failed: %s\n", + curl_easy_strerror(result)); + + /* always cleanup */ + curl_easy_cleanup(curl); + } + + curl_global_cleanup(); + return (int)result; +} diff --git a/docs/examples/cacertinmem.c b/docs/examples/cacertinmem.c index 2e43a783f5dc..841f4447d259 100644 --- a/docs/examples/cacertinmem.c +++ b/docs/examples/cacertinmem.c @@ -26,6 +26,10 @@ * */ +/* This example uses the legacy and discouraged method. Use + CURLOPT_CAINFO_BLOB instead, which is TLS backend agnostic. See the + cainfo-blob.c example */ + /* Requires: USE_OPENSSL */ #include diff --git a/docs/examples/cainfo-blob.c b/docs/examples/cainfo-blob.c new file mode 100644 index 000000000000..2f57bef37992 --- /dev/null +++ b/docs/examples/cainfo-blob.c @@ -0,0 +1,76 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ +/* + * Use CURLOPT_CAINFO_BLOB to provide a CA certificate bundle from memory. + * + */ +#include +#include + +#include + +/* Replace with actual PEM-encoded CA certificate(s) */ +static char ca_pem[] = + "-----BEGIN CERTIFICATE-----\n" + "MIIB/TCCAZSgAwIBAgIUQ4hO7G+Z+7y4G+Q5fK1u0y2K3MwwCgYIKoZIzj0EAwIw\n" + "EjEQMA4GA1UEAwwHVGVzdCBDQTAeFw0yNDA5MDEwMDAwMDBaFw0zNDA5MDEwMDAw\n" + "MDBaMBIxEDAOBgNVBAMMB1Rlc3QgQ0EwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNC\n" + "AARwS8N3R+W8d7a1q6Fz4e9j3H0q5r8s+2k6t1w5y4z8n0q2s3t4u5v6w7x8y9z0\n" + "123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUjCCAQUw\n" + "DwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU1234567890abcdef1234567890ab\n" + "-----END CERTIFICATE-----\n"; + +int main(void) +{ + CURL *curl; + CURLcode result = curl_global_init(CURL_GLOBAL_ALL); + if(result != CURLE_OK) + return (int)result; + + curl = curl_easy_init(); + if(curl) { + struct curl_blob blob; + + /* Fill the curl_blob structure */ + blob.data = (void *)ca_pem; + blob.len = strlen(ca_pem); + blob.flags = CURL_BLOB_COPY; + + curl_easy_setopt(curl, CURLOPT_URL, "https://example.com"); + + /* Pass CA bundle from memory buffer */ + curl_easy_setopt(curl, CURLOPT_CAINFO_BLOB, &blob); + + result = curl_easy_perform(curl); + if(result != CURLE_OK) + fprintf(stderr, "curl_easy_perform() failed: %s\n", + curl_easy_strerror(result)); + + /* always cleanup */ + curl_easy_cleanup(curl); + } + + curl_global_cleanup(); + return (int)result; +} diff --git a/docs/examples/clientcert-blob.c b/docs/examples/clientcert-blob.c new file mode 100644 index 000000000000..916120b1b89e --- /dev/null +++ b/docs/examples/clientcert-blob.c @@ -0,0 +1,81 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ +/* + * Use CURLOPT_SSLCERT_BLOB to provide a client certificate from memory. + * + */ +#include +#include + +#include + +/* Replace with actual PEM-encoded client certificate */ +static char cert_pem[] = + "-----BEGIN CERTIFICATE-----\n" + "MIIB/TCCAZSgAwIBAgIUQ4hO7G+Z+7y4G+Q5fK1u0y2K3MwwCgYIKoZIzj0EAwIw\n" + "EjEQMA4GA1UEAwwHVGVzdCBDQTAeFw0yNDA5MDEwMDAwMDBaFw0zNDA5MDEwMDAw\n" + "MDBaMBIxEDAOBgNVBAMMB1Rlc3QgQ0EwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNC\n" + "AARwS8N3R+W8d7a1q6Fz4e9j3H0q5r8s+2k6t1w5y4z8n0q2s3t4u5v6w7x8y9z0\n" + "123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUjCCAQUw\n" + "DwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU1234567890abcdef1234567890ab\n" + "-----END CERTIFICATE-----\n"; + +int main(void) +{ + CURL *curl; + CURLcode result = curl_global_init(CURL_GLOBAL_ALL); + if(result != CURLE_OK) + return (int)result; + + curl = curl_easy_init(); + if(curl) { + struct curl_blob cert_blob; + + /* Fill the curl_blob structure for the client certificate */ + cert_blob.data = (void *)cert_pem; + cert_blob.len = strlen(cert_pem); + cert_blob.flags = CURL_BLOB_COPY; + + curl_easy_setopt(curl, CURLOPT_URL, "https://example.com"); + + /* Pass client certificate from memory */ + curl_easy_setopt(curl, CURLOPT_SSLCERT_BLOB, &cert_blob); + curl_easy_setopt(curl, CURLOPT_SSLCERTTYPE, "PEM"); + + /* Specify the private key file or use CURLOPT_SSLKEY_BLOB */ + curl_easy_setopt(curl, CURLOPT_SSLKEY, "client.key"); + curl_easy_setopt(curl, CURLOPT_KEYPASSWD, "s3cret"); + + result = curl_easy_perform(curl); + if(result != CURLE_OK) + fprintf(stderr, "curl_easy_perform() failed: %s\n", + curl_easy_strerror(result)); + + /* always cleanup */ + curl_easy_cleanup(curl); + } + + curl_global_cleanup(); + return (int)result; +} diff --git a/docs/examples/conditional.c b/docs/examples/conditional.c new file mode 100644 index 000000000000..0d930179a2ad --- /dev/null +++ b/docs/examples/conditional.c @@ -0,0 +1,70 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ +/* + * Use CURLOPT_TIMEVALUE to perform a conditional HTTP request. + * + */ +#include + +#include + +int main(void) +{ + CURL *curl; + CURLcode result = curl_global_init(CURL_GLOBAL_ALL); + if(result != CURLE_OK) + return (int)result; + + curl = curl_easy_init(); + if(curl) { + long unmet = 0; + + curl_easy_setopt(curl, CURLOPT_URL, "https://example.com"); + + /* January 1, 2020 00:00:00 UTC */ + curl_easy_setopt(curl, CURLOPT_TIMEVALUE, 1577836800L); + + /* If-Modified-Since the above timestamp */ + curl_easy_setopt(curl, CURLOPT_TIMECONDITION, + (long)CURL_TIMECOND_IFMODSINCE); + + result = curl_easy_perform(curl); + if(result != CURLE_OK) { + fprintf(stderr, "curl_easy_perform() failed: %s\n", + curl_easy_strerror(result)); + } + else { + /* Check whether the condition was met or unmet + (e.g. 304 Not Modified) */ + if(curl_easy_getinfo(curl, CURLINFO_CONDITION_UNMET, &unmet) == CURLE_OK) + printf("Condition was %sfulfilled\n", unmet ? "NOT " : ""); + } + + /* always cleanup */ + curl_easy_cleanup(curl); + } + + curl_global_cleanup(); + return (int)result; +} diff --git a/docs/examples/cookie.c b/docs/examples/cookie.c new file mode 100644 index 000000000000..413a49925b3d --- /dev/null +++ b/docs/examples/cookie.c @@ -0,0 +1,59 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ +/* + * Send specific cookies + * + */ +#include + +#include + +int main(void) +{ + CURL *curl; + + CURLcode result = curl_global_init(CURL_GLOBAL_ALL); + if(result != CURLE_OK) + return (int)result; + + curl = curl_easy_init(); + if(curl) { + curl_easy_setopt(curl, CURLOPT_URL, "https://example.com"); + + /* pass on exactly the cookies we want */ + curl_easy_setopt(curl, CURLOPT_COOKIE, "tool=curl; fun=yes;"); + + /* Perform the request, result gets the return code */ + result = curl_easy_perform(curl); + /* Check for errors */ + if(result != CURLE_OK) + fprintf(stderr, "curl_easy_perform() failed: %s\n", + curl_easy_strerror(result)); + + /* always cleanup */ + curl_easy_cleanup(curl); + } + curl_global_cleanup(); + return 0; +} diff --git a/docs/examples/cookiejar.c b/docs/examples/cookiejar.c new file mode 100644 index 000000000000..836062e7b943 --- /dev/null +++ b/docs/examples/cookiejar.c @@ -0,0 +1,63 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ +/* + * HTTP with cookies from and to a jar + * + */ +#include + +#include + +int main(void) +{ + CURL *curl; + + CURLcode result = curl_global_init(CURL_GLOBAL_ALL); + if(result != CURLE_OK) + return (int)result; + + curl = curl_easy_init(); + if(curl) { + curl_easy_setopt(curl, CURLOPT_URL, "https://example.com"); + + /* get cookies from an existing file */ + curl_easy_setopt(curl, CURLOPT_COOKIEFILE, "cookie-read.txt"); + + /* export cookies to this file when closing the handle. Can be the + same file as we read from, or a different one */ + curl_easy_setopt(curl, CURLOPT_COOKIEJAR, "cookie-write.txt"); + + /* Perform the request, result gets the return code */ + result = curl_easy_perform(curl); + /* Check for errors */ + if(result != CURLE_OK) + fprintf(stderr, "curl_easy_perform() failed: %s\n", + curl_easy_strerror(result)); + + /* always cleanup */ + curl_easy_cleanup(curl); + } + curl_global_cleanup(); + return 0; +} diff --git a/docs/examples/doh.c b/docs/examples/doh.c new file mode 100644 index 000000000000..a96820011cde --- /dev/null +++ b/docs/examples/doh.c @@ -0,0 +1,60 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ +/* + * Use CURLOPT_DOH_URL to resolve hostnames using DNS-over-HTTPS. + * + */ +#include + +#include + +int main(void) +{ + CURL *curl; + CURLcode result = curl_global_init(CURL_GLOBAL_ALL); + if(result != CURLE_OK) + return (int)result; + + curl = curl_easy_init(); + if(curl) { + /* Set the target URL to fetch */ + curl_easy_setopt(curl, CURLOPT_URL, "https://example.com"); + + /* Specify the DNS-over-HTTPS (DoH) server URL used to resolve names */ + curl_easy_setopt(curl, CURLOPT_DOH_URL, + "https://cloudflare-dns.com/dns-query"); + + /* Perform the request using the configured DoH resolver */ + result = curl_easy_perform(curl); + if(result != CURLE_OK) + fprintf(stderr, "curl_easy_perform() failed: %s\n", + curl_easy_strerror(result)); + + /* Always cleanup easy handle */ + curl_easy_cleanup(curl); + } + + curl_global_cleanup(); + return (int)result; +} diff --git a/docs/examples/ftp-listonly.c b/docs/examples/ftp-listonly.c new file mode 100644 index 000000000000..c407bb40a6a2 --- /dev/null +++ b/docs/examples/ftp-listonly.c @@ -0,0 +1,58 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ +/* + * List only filenames of a remote FTP directory using CURLOPT_DIRLISTONLY. + * + */ +#include + +#include + +int main(void) +{ + CURL *curl; + CURLcode result = curl_global_init(CURL_GLOBAL_ALL); + if(result != CURLE_OK) + return (int)result; + + curl = curl_easy_init(); + if(curl) { + /* Set the FTP directory URL (trailing slash designates a directory) */ + curl_easy_setopt(curl, CURLOPT_URL, "ftp://example.com/dir/"); + + /* Request names only (causes NLST to be sent instead of LIST) */ + curl_easy_setopt(curl, CURLOPT_DIRLISTONLY, 1L); + + result = curl_easy_perform(curl); + if(result != CURLE_OK) + fprintf(stderr, "curl_easy_perform() failed: %s\n", + curl_easy_strerror(result)); + + /* always cleanup */ + curl_easy_cleanup(curl); + } + + curl_global_cleanup(); + return (int)result; +} diff --git a/docs/examples/httpput.c b/docs/examples/httpput.c index 90cb81b7de22..0c13490cc36f 100644 --- a/docs/examples/httpput.c +++ b/docs/examples/httpput.c @@ -129,6 +129,10 @@ int main(int argc, const char *argv[]) curl_easy_setopt(curl, CURLOPT_INFILESIZE_LARGE, (curl_off_t)file_info.st_size); + /* ask libcurl to use a larger size for the upload buffer */ + curl_easy_setopt(curl, CURLOPT_UPLOAD_BUFFERSIZE, + (long)400000); + /* Now run off and do what you have been told! */ result = curl_easy_perform(curl); /* Check for errors */ diff --git a/docs/examples/httpsig.c b/docs/examples/httpsig.c new file mode 100644 index 000000000000..6d9ebb36ac0e --- /dev/null +++ b/docs/examples/httpsig.c @@ -0,0 +1,78 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ +/* + * Send an HTTP request signed with RFC 9421 HTTP Message Signatures. + * + */ +#include + +#include + +int main(void) +{ + CURL *curl; + CURLcode result = curl_global_init(CURL_GLOBAL_ALL); + if(result != CURLE_OK) + return (int)result; + + curl = curl_easy_init(); + if(curl) { + struct curl_slist *headers = NULL; + + /* Add a custom header that gets signed */ + headers = curl_slist_append(NULL, "Content-Type: application/json"); + if(headers) { + curl_easy_setopt(curl, CURLOPT_URL, "https://example.com/api"); + curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); + + /* Select signing algorithm (e.g. Ed25519) */ + curl_easy_setopt(curl, CURLOPT_HTTPSIG_ALGORITHM, CURLHTTPSIG_ED25519); + + /* Hex-encoded 32-byte Ed25519 seed */ + curl_easy_setopt(curl, CURLOPT_HTTPSIG_KEY, + "9f8362f87a484a954e6e740c5b4c0e84" + "229139a20aa8ab56ff66586f6a7d29c5"); + + /* Key identifier sent in Signature-Input */ + curl_easy_setopt(curl, CURLOPT_HTTPSIG_KEYID, "my-key-id"); + + /* Components and headers to include in the signature */ + curl_easy_setopt(curl, CURLOPT_HTTPSIG_HEADERS, + "method authority path content-type:"); + + result = curl_easy_perform(curl); + if(result != CURLE_OK) + fprintf(stderr, "curl_easy_perform() failed: %s\n", + curl_easy_strerror(result)); + + curl_slist_free_all(headers); + } + + /* always cleanup */ + curl_easy_cleanup(curl); + } + + curl_global_cleanup(); + return (int)result; +} diff --git a/docs/examples/limit-rate.c b/docs/examples/limit-rate.c new file mode 100644 index 000000000000..b0a72859d370 --- /dev/null +++ b/docs/examples/limit-rate.c @@ -0,0 +1,59 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ +/* + * Download with network speed limit + * + */ +#include + +#include + +int main(void) +{ + CURL *curl; + + CURLcode result = curl_global_init(CURL_GLOBAL_ALL); + if(result != CURLE_OK) + return (int)result; + + curl = curl_easy_init(); + if(curl) { + curl_easy_setopt(curl, CURLOPT_URL, "https://example.com"); + + /* limit the transfer speed to no more than this many bytes per second */ + curl_easy_setopt(curl, CURLOPT_MAX_RECV_SPEED_LARGE, (curl_off_t)40000); + + /* Perform the request, result gets the return code */ + result = curl_easy_perform(curl); + /* Check for errors */ + if(result != CURLE_OK) + fprintf(stderr, "curl_easy_perform() failed: %s\n", + curl_easy_strerror(result)); + + /* always cleanup */ + curl_easy_cleanup(curl); + } + curl_global_cleanup(); + return 0; +} diff --git a/docs/examples/nagle.c b/docs/examples/nagle.c new file mode 100644 index 000000000000..71ad00b9cdd0 --- /dev/null +++ b/docs/examples/nagle.c @@ -0,0 +1,59 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ +/* + * Switch on the TCP Nagle algorithm with CURLOPT_TCP_NODELAY + * + */ +#include + +#include + +int main(void) +{ + CURL *curl; + + CURLcode result = curl_global_init(CURL_GLOBAL_ALL); + if(result != CURLE_OK) + return (int)result; + + curl = curl_easy_init(); + if(curl) { + curl_easy_setopt(curl, CURLOPT_URL, "https://example.com"); + + /* leave Nagle enabled */ + curl_easy_setopt(curl, CURLOPT_TCP_NODELAY, 0L); + + /* Perform the request, result gets the return code */ + result = curl_easy_perform(curl); + /* Check for errors */ + if(result != CURLE_OK) + fprintf(stderr, "curl_easy_perform() failed: %s\n", + curl_easy_strerror(result)); + + /* always cleanup */ + curl_easy_cleanup(curl); + } + curl_global_cleanup(); + return 0; +} diff --git a/docs/examples/path-as-is.c b/docs/examples/path-as-is.c new file mode 100644 index 000000000000..112c8e0ccba9 --- /dev/null +++ b/docs/examples/path-as-is.c @@ -0,0 +1,60 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ +/* + * Keep dot-dot sequences in the path in HTTP request. + * + */ +#include + +#include + +int main(void) +{ + CURL *curl; + + CURLcode result = curl_global_init(CURL_GLOBAL_ALL); + if(result != CURLE_OK) + return (int)result; + + curl = curl_easy_init(); + if(curl) { + curl_easy_setopt(curl, CURLOPT_URL, + "https://example.com/../../etc/passwd"); + + /* we actually want to keep the ../ sequence in the path */ + curl_easy_setopt(curl, CURLOPT_PATH_AS_IS, 1L); + + /* Perform the request, result gets the return code */ + result = curl_easy_perform(curl); + /* Check for errors */ + if(result != CURLE_OK) + fprintf(stderr, "curl_easy_perform() failed: %s\n", + curl_easy_strerror(result)); + + /* always cleanup */ + curl_easy_cleanup(curl); + } + curl_global_cleanup(); + return 0; +} diff --git a/docs/examples/trailer.c b/docs/examples/trailer.c new file mode 100644 index 000000000000..8ee870cfa457 --- /dev/null +++ b/docs/examples/trailer.c @@ -0,0 +1,95 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ +/* + * Send HTTP POST with trailing headers using CURLOPT_TRAILERFUNCTION. + * + */ +#include +#include + +#include + +/* Callback invoked right before the final CRLF in chunked transfer */ +static int trailer_cb(struct curl_slist **list, void *userdata) +{ + struct curl_slist *nlist; + (void)userdata; + + /* Append the trailing header; libcurl frees the slist on completion */ + nlist = curl_slist_append(*list, "Checksum: 0123456789abcdef"); + if(!nlist) + return CURL_TRAILERFUNC_ABORT; + + *list = nlist; + return CURL_TRAILERFUNC_OK; +} + +int main(void) +{ + CURL *curl; + CURLcode result = curl_global_init(CURL_GLOBAL_ALL); + if(result != CURLE_OK) + return (int)result; + + curl = curl_easy_init(); + if(curl) { + static const char post_data[] = "field1=value1&field2=value2"; + struct curl_slist *headers = NULL; + struct curl_slist *nheaders; + + /* Declare expected trailers and force chunked transfer-encoding */ + nheaders = curl_slist_append(headers, "Trailer: Checksum"); + if(nheaders) { + headers = nheaders; + nheaders = curl_slist_append(headers, "Transfer-Encoding: chunked"); + } + + if(nheaders) { + headers = nheaders; + curl_easy_setopt(curl, CURLOPT_URL, "https://example.com/upload"); + curl_easy_setopt(curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1); + curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); + + /* Configure the POST data */ + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, post_data); + curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, + (long)sizeof(post_data) - 1); + + /* Set callback to populate trailing headers */ + curl_easy_setopt(curl, CURLOPT_TRAILERFUNCTION, trailer_cb); + curl_easy_setopt(curl, CURLOPT_TRAILERDATA, NULL); + + result = curl_easy_perform(curl); + if(result != CURLE_OK) + fprintf(stderr, "curl_easy_perform() failed: %s\n", + curl_easy_strerror(result)); + } + + curl_slist_free_all(headers); + curl_easy_cleanup(curl); + } + + curl_global_cleanup(); + return (int)result; +} diff --git a/docs/examples/usercertinmem.c b/docs/examples/usercertinmem.c index 3e7865780f15..d9e8e40b5532 100644 --- a/docs/examples/usercertinmem.c +++ b/docs/examples/usercertinmem.c @@ -26,11 +26,10 @@ * */ -/* Written by Ishan SinghLevett, based on Theo Borm's cacertinmem.c. Note that - * to maintain simplicity this example does not use a CA certificate for peer - * verification. Some form of peer verification must be used in real - * circumstances when a secure connection is required. - */ +/* This example uses the legacy and discouraged method. It also disables + server certificate verification; real applications must verify the peer. + Use CURLOPT_SSLCERT_BLOB instead, which is TLS backend agnostic. See the + clientcert-blob.c example. */ /* Requires: USE_OPENSSL */ From a4d6184da08c00929e72375fd921c0f02ca9a3af Mon Sep 17 00:00:00 2001 From: Daniel Wade Date: Sat, 10 Oct 2026 22:31:36 +1030 Subject: [PATCH 471/481] tool_getparam: restore --socks5-gssapi-service The option was accepted but ignored since the switch to an enum for every option. Make it set the proxy service name again, as --proxy-service-name does. Extend test 1481 to verify Follow-up to 9e4e527735bfd92132d102b2e7d102b8cb4f5a61 Closes #23344 --- src/tool_getparam.c | 1 + tests/data/data1481.c | 1 + tests/data/test1481 | 2 +- 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/tool_getparam.c b/src/tool_getparam.c index 50330fc02c25..44825f83f21f 100644 --- a/src/tool_getparam.c +++ b/src/tool_getparam.c @@ -2692,6 +2692,7 @@ static ParameterError opt_string(struct OperationConfig *config, err = getstr(&config->sasl_authzid, nextarg, DENY_BLANK); break; case C_PROXY_SERVICE_NAME: /* --proxy-service-name */ + case C_SOCKS5_GSSAPI_SERVICE: /* --socks5-gssapi-service */ err = getstr(&config->proxy_service_name, nextarg, DENY_BLANK); break; case C_SERVICE_NAME: /* --service-name */ diff --git a/tests/data/data1481.c b/tests/data/data1481.c index e7d468e6985c..7d09b351bc8b 100644 --- a/tests/data/data1481.c +++ b/tests/data/data1481.c @@ -14,6 +14,7 @@ int main(int argc, char *argv[]) curl_easy_setopt(curl, CURLOPT_BUFFERSIZE, 102400L); curl_easy_setopt(curl, CURLOPT_URL, "http://moo/"); curl_easy_setopt(curl, CURLOPT_PROXY, "http://%HOSTIP:%HTTPPORT"); + curl_easy_setopt(curl, CURLOPT_PROXY_SERVICE_NAME, "sockd"); curl_easy_setopt(curl, CURLOPT_USERAGENT, "curl/%VERSION"); curl_easy_setopt(curl, CURLOPT_MAXREDIRS, 50L); curl_easy_setopt(curl, CURLOPT_SSLVERSION, (long)(CURL_SSLVERSION_TLSv1_2 | CURL_SSLVERSION_MAX_TLSv1_3)); diff --git a/tests/data/test1481 b/tests/data/test1481 index 8a0d31078533..3991d75a4f8d 100644 --- a/tests/data/test1481 +++ b/tests/data/test1481 @@ -37,7 +37,7 @@ SSL SSL_CERT_FILE -http://moo/ --libcurl %LOGDIR/test%TESTNUMBER.c --tls-max 1.3 --proxy-tlsv1 -x http://%HOSTIP:%HTTPPORT +http://moo/ --libcurl %LOGDIR/test%TESTNUMBER.c --tls-max 1.3 --proxy-tlsv1 -x http://%HOSTIP:%HTTPPORT --socks5-gssapi-service sockd From e8ca163de2c705c416292ac6f044768c44e8b9ad Mon Sep 17 00:00:00 2001 From: Daniel Stenberg Date: Tue, 13 Oct 2026 15:12:35 +0200 Subject: [PATCH 472/481] CI: add Scorecard workflow Previous attempt: #21349 Assisted-by: Viktor Szakats Closes #23387 --- .github/workflows/scorecard.yml | 58 +++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 .github/workflows/scorecard.yml diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml new file mode 100644 index 000000000000..0575bfa21522 --- /dev/null +++ b/.github/workflows/scorecard.yml @@ -0,0 +1,58 @@ +# Copyright (C) Daniel Stenberg, , et al. +# +# SPDX-License-Identifier: curl + +name: 'OpenSSF Scorecard' + +'on': + # For Branch-Protection check. Only the default branch is supported. See + # https://github.com/ossf/scorecard/blob/main/docs/checks.md#branch-protection + branch_protection_rule: + # To guarantee Maintained check is occasionally updated. See + # https://github.com/ossf/scorecard/blob/main/docs/checks.md#maintained + schedule: + - cron: '15 14 * * 0' + push: + branches: ['master'] + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }} + cancel-in-progress: true + +permissions: {} +cache-mode: none + +jobs: + analysis: + name: Scorecard analysis + runs-on: ubuntu-latest + # `publish_results: true` only works when run from the default branch. conditional can be removed if disabled. + if: ${{ github.event.repository.default_branch == github.ref_name || github.event_name == 'pull_request' }} + permissions: + security-events: write # to upload the results to code-scanning dashboard. + id-token: write # Needed to publish results and get a badge (see publish_results below). + + steps: + - name: 'Checkout code' + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + + - name: 'Run analysis' + uses: ossf/scorecard-action@fa7117db83c52d787ae5a89f350999701505d78e # v2.5.0 + with: + results_file: results.sarif + results_format: sarif + publish_results: true + + - name: 'Upload artifact' + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: SARIF file + path: results.sarif + retention-days: 5 + + - name: 'Upload to code-scanning' + uses: github/codeql-action/upload-sarif@2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2 # v4.38.2 + with: + sarif_file: results.sarif From 5a5f81d57a44efa1ba3d62941258955f817f6a17 Mon Sep 17 00:00:00 2001 From: Viktor Szakats Date: Wed, 2 Sep 2026 13:50:53 +0200 Subject: [PATCH 473/481] tidy-up: miscellaneous - add more `manually bumped` comments. Follow-up to b783adf8cf706f0c1c1a50f0812ad10de888dcb3 #23271 - add link to Circle CI runner image page. - 'zero byte'/'binary 0'/'NUL' -> 'NUL byte' for consistency. - appveyor: use long-form options, `--out-null`, add `--disable`. - whitespace/typo/comment fixes. Closes #23386 --- .circleci/config.yml | 7 ++++--- .github/workflows/http3-linux.yml | 2 +- .github/workflows/scorecard.yml | 2 +- appveyor.sh | 10 +++++----- lib/curl_config-cmake.h.in | 2 +- lib/dllmain.c | 4 ++-- lib/mprintf.c | 2 +- lib/thrdqueue.h | 8 ++++---- lib/urldata.h | 2 +- lib/vdns/doh.c | 2 +- lib/vtls/hostcheck.c | 2 +- src/curlinfo.c | 7 ++++--- tests/appveyor.pm | 2 +- tests/azure.pm | 2 +- tests/http/conftest.py | 2 +- tests/http/test_60_h3_proxy.py | 2 +- tests/http/testenv/env.py | 2 +- tests/http/testenv/h2o.py | 2 +- tests/libtest/lib544.c | 2 +- tests/runtests.pl | 2 -- tests/secureserver.pl | 2 +- tests/server/tftpd.c | 6 +++--- tests/sshhelp.pm | 6 +++--- tests/tunit/CMakeLists.txt | 2 +- tests/unit/CMakeLists.txt | 2 +- tests/unit/unit1397.c | 2 +- 26 files changed, 43 insertions(+), 43 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index b34474d1e2fa..db79f85fcc57 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -23,6 +23,7 @@ ########################################################################### # View these jobs in the browser: https://app.circleci.com/pipelines/github/curl/curl +# Images: https://circleci.com/developer/images?imageType=machine # Use the latest 2.1 version of CircleCI pipeline process engine. See: https://circleci.com/docs/reference/configuration-reference/ version: 2.1 @@ -111,7 +112,7 @@ commands: executors: ubuntu: machine: - image: ubuntu-2204:2026.05.1 + image: ubuntu-2204:2026.05.1 # manually bumped jobs: basic: @@ -154,7 +155,7 @@ jobs: arm: machine: - image: ubuntu-2204:2026.05.1 + image: ubuntu-2204:2026.05.1 # manually bumped resource_class: arm.medium steps: - checkout @@ -165,7 +166,7 @@ jobs: arm-cares: machine: - image: ubuntu-2204:2026.05.1 + image: ubuntu-2204:2026.05.1 # manually bumped resource_class: arm.medium steps: - checkout diff --git a/.github/workflows/http3-linux.yml b/.github/workflows/http3-linux.yml index 2bfabaf05810..0e9e40db17e4 100644 --- a/.github/workflows/http3-linux.yml +++ b/.github/workflows/http3-linux.yml @@ -63,7 +63,7 @@ env: NGTCP2_VERSION: 1.25.0 # renovate: datasource=github-tags depName=nghttp2/nghttp2 NGHTTP2_VERSION: 1.70.0 - # no tagged releases + # manually bumped (repo not offering tagged releases) H2O_VERSION: 11b0cfa2771e3ccad4a852e72473e4e278ab1de7 # 2026-05-28 H2O_SHA256: 5ae1bd7b09970d7d49c41fa68193e24da04c2a7ac5581fbe2affc79200b0721f diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index 0575bfa21522..1462d0885e01 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -24,7 +24,7 @@ cache-mode: none jobs: analysis: - name: Scorecard analysis + name: 'Scorecard analysis' runs-on: ubuntu-latest # `publish_results: true` only works when run from the default branch. conditional can be removed if disabled. if: ${{ github.event.repository.default_branch == github.ref_name || github.event_name == 'pull_request' }} diff --git a/appveyor.sh b/appveyor.sh index 5c43e7c17947..a22d17bf7401 100644 --- a/appveyor.sh +++ b/appveyor.sh @@ -40,11 +40,11 @@ if [ -n "${CMAKE_GENERATOR:-}" ]; then esac if [ "${APPVEYOR_BUILD_WORKER_IMAGE}" = 'Visual Studio 2026' ]; then - openssl_root_win="C:/OpenSSL-v36${openssl_suffix}" + openssl_root_win="C:/OpenSSL-v36${openssl_suffix}" # manually bumped elif [ "${APPVEYOR_BUILD_WORKER_IMAGE}" = 'Visual Studio 2022' ]; then - openssl_root_win="C:/OpenSSL-v35${openssl_suffix}" + openssl_root_win="C:/OpenSSL-v35${openssl_suffix}" # manually bumped elif [ "${APPVEYOR_BUILD_WORKER_IMAGE}" = 'Visual Studio 2019' ]; then - openssl_root_win="C:/OpenSSL-v30${openssl_suffix}" + openssl_root_win="C:/OpenSSL-v30${openssl_suffix}" # manually bumped fi [ -n "${openssl_root_win:-}" ] && openssl_root="$(cygpath "${openssl_root_win}")" @@ -116,9 +116,9 @@ fi # create artifact if [[ "${CREATE_ARTIFACT:-}" = 'true' ]]; then - cp /usr/ssl/certs/ca-bundle.crt curl-ca-bundle.crt + cp -p /usr/ssl/certs/ca-bundle.crt curl-ca-bundle.crt echo 'Checking that https works (it should find curl-ca-bundle.crt if needed)' - "${curl}" -v -fsS --retry 6 --retry-all-errors -o /dev/null https://curl.se/ + "${curl}" --disable --verbose --fail --silent --show-error --retry 6 --retry-all-errors --out-null https://curl.se/ if [ -n "${APPVEYOR_PULL_REQUEST_NUMBER:-}" ]; then archive="curl_pr${APPVEYOR_PULL_REQUEST_NUMBER}_${APPVEYOR_PULL_REQUEST_HEAD_COMMIT}.zip" else diff --git a/lib/curl_config-cmake.h.in b/lib/curl_config-cmake.h.in index 9ba3b42ec409..ba10af5697f6 100644 --- a/lib/curl_config-cmake.h.in +++ b/lib/curl_config-cmake.h.in @@ -695,7 +695,7 @@ ${SIZEOF_TIME_T_CODE} /* if libpsl is in use */ #cmakedefine USE_LIBPSL 1 -/* if you want to use OpenLDAP code instead of legacy ldap implementation */ +/* if you want to use OpenLDAP code instead of legacy LDAP implementation */ #cmakedefine USE_OPENLDAP 1 /* if OpenSSL is in use */ diff --git a/lib/dllmain.c b/lib/dllmain.c index 5aa6565010d8..cee1801b4f9a 100644 --- a/lib/dllmain.c +++ b/lib/dllmain.c @@ -33,7 +33,7 @@ #if defined(USE_OPENSSL) && \ !defined(OPENSSL_IS_AWSLC) && !defined(OPENSSL_IS_BORINGSSL) && \ !defined(LIBRESSL_VERSION_NUMBER) -#define PREVENT_OPENSSL_MEMLEAK +#define PREVENT_OPENSSL_MEMLEAK /* non-fork OpenSSL */ #endif #ifdef PREVENT_OPENSSL_MEMLEAK @@ -59,6 +59,6 @@ BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) } return TRUE; } -#endif /* USE_OPENSSL (non-fork) */ +#endif /* PREVENT_OPENSSL_MEMLEAK */ #endif /* _WIN32 && !CURL_STATICLIB */ diff --git a/lib/mprintf.c b/lib/mprintf.c index 571c4175015d..9f172c377bba 100644 --- a/lib/mprintf.c +++ b/lib/mprintf.c @@ -812,7 +812,7 @@ static bool out_double(void *userp, #ifdef _WIN32 curlx_win32_snprintf(work, BUFFSIZE, fmt, dnum); #else - /* !checksrc! disable BANNEDFUNC 2 */ + /* !checksrc! disable BANNEDFUNC 1 */ snprintf(work, BUFFSIZE, fmt, dnum); #endif #ifdef CURL_HAVE_DIAG diff --git a/lib/thrdqueue.h b/lib/thrdqueue.h index ffa93d36b811..ebd0c0785553 100644 --- a/lib/thrdqueue.h +++ b/lib/thrdqueue.h @@ -120,13 +120,13 @@ CURLcode Curl_thrdq_set_props(struct curl_thrdq *tqueue, #ifdef CURLVERBOSE void Curl_thrdq_trace(struct curl_thrdq *tqueue, struct Curl_easy *data); -#define CURL_THRDQ_TRACE(t,d) \ +#define CURL_THRDQ_TRACE(t, d) \ do { \ - if(Curl_trc_ft_is_verbose((d), &Curl_trc_feat_threads)) \ - Curl_thrdq_trace((t), (d)); \ + if(Curl_trc_ft_is_verbose(d, &Curl_trc_feat_threads)) \ + Curl_thrdq_trace(t, d); \ } while(0) #else -#define CURL_THRDQ_TRACE(t,d) Curl_nop_stmt +#define CURL_THRDQ_TRACE(t, d) Curl_nop_stmt #endif #endif /* USE_THREADS */ diff --git a/lib/urldata.h b/lib/urldata.h index d69233641e25..249331d30bbc 100644 --- a/lib/urldata.h +++ b/lib/urldata.h @@ -840,7 +840,7 @@ struct UserDefined { curl_seek_callback seek_func; /* function that seeks the input */ curl_off_t postfieldsize; /* if POST, this might have a size to use instead of strlen(), and then the data *may* be binary - (contain zero bytes) */ + (contain NUL bytes) */ curl_write_callback fwrite_func; /* function that stores the output */ curl_write_callback fwrite_header; /* function that stores headers */ curl_write_callback fwrite_rtp; /* function that stores interleaved RTP */ diff --git a/lib/vdns/doh.c b/lib/vdns/doh.c index 57d8826a5a3d..f479f0d2beb3 100644 --- a/lib/vdns/doh.c +++ b/lib/vdns/doh.c @@ -123,7 +123,7 @@ UNITTEST DOHcode doh_req_encode(const char *host, * Each [ label, dot ] pair is encoded as [ length, label ], * preserving overall length. A final [ label ] without a dot is * also encoded as [ length, label ], increasing overall length - * by one. The encoding is completed by appending a zero byte, + * by one. The encoding is completed by appending a NUL byte, * representing the zero-length root label, again increasing * the overall length by one. */ diff --git a/lib/vtls/hostcheck.c b/lib/vtls/hostcheck.c index 32690a80ef84..ec36cb5ba50d 100644 --- a/lib/vtls/hostcheck.c +++ b/lib/vtls/hostcheck.c @@ -38,7 +38,7 @@ #include "vtls/hostcheck.h" /* check the two input strings with given length, but do not - assume they end in nul-bytes */ + assume they are null-terminated */ static bool pmatch(const char *hostname, size_t hostlen, const char *pattern, size_t patternlen) { diff --git a/src/curlinfo.c b/src/curlinfo.c index 87f00032b139..8eee76358ff5 100644 --- a/src/curlinfo.c +++ b/src/curlinfo.c @@ -224,9 +224,10 @@ static const char * const disabled[] = { , "win32-ca-searchpath: " -#if !defined(_WIN32) || \ - (defined(CURL_WINDOWS_UWP) || \ - defined(CURL_DISABLE_CA_SEARCH) || defined(CURL_CA_SEARCH_SAFE)) +#if !defined(_WIN32) || \ + (defined(CURL_WINDOWS_UWP) || \ + defined(CURL_DISABLE_CA_SEARCH) || \ + defined(CURL_CA_SEARCH_SAFE)) "OFF" #else "ON" diff --git a/tests/appveyor.pm b/tests/appveyor.pm index 6ea2cccd3906..dcd5fe02222a 100644 --- a/tests/appveyor.pm +++ b/tests/appveyor.pm @@ -79,7 +79,7 @@ sub appveyor_update_test_result { if(!defined $stop) { $stop = $start; } - my $appveyor_duration = sprintf("%.0f", ($stop-$start)*1000); + my $appveyor_duration = sprintf("%.0f", ($stop - $start) * 1000); my $appveyor_outcome; my $appveyor_category; if($error == 2) { diff --git a/tests/azure.pm b/tests/azure.pm index 623de969b98c..7bb7946c965c 100644 --- a/tests/azure.pm +++ b/tests/azure.pm @@ -112,7 +112,7 @@ sub azure_update_test_result { } my $azure_start = strftime "%Y-%m-%dT%H:%M:%SZ", gmtime $start; my $azure_complete = strftime "%Y-%m-%dT%H:%M:%SZ", gmtime $stop; - my $azure_duration = sprintf("%.0f", ($stop-$start)*1000); + my $azure_duration = sprintf("%.0f", ($stop - $start) * 1000); my $azure_outcome; if($error == 2) { $azure_outcome = 'NotApplicable'; diff --git a/tests/http/conftest.py b/tests/http/conftest.py index 4e6bdd8163d7..ffa2792deede 100644 --- a/tests/http/conftest.py +++ b/tests/http/conftest.py @@ -1,4 +1,4 @@ -# *************************************************************************** +#*************************************************************************** # _ _ ____ _ # Project ___| | | | _ \| | # / __| | | | |_) | | diff --git a/tests/http/test_60_h3_proxy.py b/tests/http/test_60_h3_proxy.py index 9b9131ae3592..c31f9668aca8 100644 --- a/tests/http/test_60_h3_proxy.py +++ b/tests/http/test_60_h3_proxy.py @@ -1,4 +1,4 @@ -# *************************************************************************** +#*************************************************************************** # _ _ ____ _ # Project ___| | | | _ \| | # / __| | | | |_) | | diff --git a/tests/http/testenv/env.py b/tests/http/testenv/env.py index c11d55669957..1f31485db276 100644 --- a/tests/http/testenv/env.py +++ b/tests/http/testenv/env.py @@ -1,4 +1,4 @@ -# *************************************************************************** +#*************************************************************************** # _ _ ____ _ # Project ___| | | | _ \| | # / __| | | | |_) | | diff --git a/tests/http/testenv/h2o.py b/tests/http/testenv/h2o.py index 377e84914dd4..64c24eba2779 100644 --- a/tests/http/testenv/h2o.py +++ b/tests/http/testenv/h2o.py @@ -1,4 +1,4 @@ -# *************************************************************************** +#*************************************************************************** # _ _ ____ _ # Project ___| | | | _ \| | # / __| | | | |_) | | diff --git a/tests/libtest/lib544.c b/tests/libtest/lib544.c index 013c2ab32b30..42fa8dd51815 100644 --- a/tests/libtest/lib544.c +++ b/tests/libtest/lib544.c @@ -73,7 +73,7 @@ static CURLcode test_lib544(const char *URL) curl = curl2; } - /* Now, this is a POST request with binary 0 embedded in POST data. */ + /* Now, this is a POST request with NUL byte embedded in POST data. */ result = curl_easy_perform(curl); test_cleanup: diff --git a/tests/runtests.pl b/tests/runtests.pl index 901c8b047f2b..0d997f1ed4c1 100755 --- a/tests/runtests.pl +++ b/tests/runtests.pl @@ -1463,7 +1463,6 @@ sub singletest_check { if($res) { return -1; } - $ok .= "p"; } else { @@ -1624,7 +1623,6 @@ sub singletest_check { if($res) { return -1; } - $ok .= "P"; } else { diff --git a/tests/secureserver.pl b/tests/secureserver.pl index 9b12b4425a6c..2429d7bfb619 100755 --- a/tests/secureserver.pl +++ b/tests/secureserver.pl @@ -233,7 +233,7 @@ sub exit_signal_handler { } exit 1; } -$stunnel_version = (100*$ver_major) + $ver_minor; +$stunnel_version = (100 * $ver_major) + $ver_minor; #*************************************************************************** # Verify minimum stunnel required version diff --git a/tests/server/tftpd.c b/tests/server/tftpd.c index 0ca6aca83e7d..3cb63ce708bb 100644 --- a/tests/server/tftpd.c +++ b/tests/server/tftpd.c @@ -196,7 +196,7 @@ static tftphdr_storage_t ackbuf; static curl_socket_t peer = CURL_SOCKET_BAD; static unsigned int timeout; -static unsigned int maxtimeout = 5 * TIMEOUT; +static unsigned int maxtimeout = TIMEOUT * 5; static int tftpd_wrotepidfile = 0; static int tftpd_wroteportfile = 0; @@ -984,7 +984,7 @@ static int do_tftp(struct testcase *test, struct tftphdr *tp, ssize_t size) recvtimeout = sizeof(recvtimeoutbak); getsockopt(peer, SOL_SOCKET, SO_RCVTIMEO, (char *)&recvtimeoutbak, (int *)&recvtimeout); - recvtimeout = TIMEOUT*1000; + recvtimeout = TIMEOUT * 1000; setsockopt(peer, SOL_SOCKET, SO_RCVTIMEO, (const char *)&recvtimeout, sizeof(recvtimeout)); #endif @@ -1175,7 +1175,7 @@ static int test_tftpd(int argc, const char **argv) } } - maxtimeout = 5 * TIMEOUT; + maxtimeout = TIMEOUT * 5; tp = &trsbuf.hdr; tp->th_opcode = ntohs(tp->th_opcode); diff --git a/tests/sshhelp.pm b/tests/sshhelp.pm index 2481de3278de..84c9a8513fba 100644 --- a/tests/sshhelp.pm +++ b/tests/sshhelp.pm @@ -383,7 +383,7 @@ sub sshversioninfo { $minor = $2; $patch = $4 ? $4 : 0; $sshid = 'OpenSSH'; - $versnum = (100*$major) + (10*$minor) + $patch; + $versnum = (100 * $major) + (10 * $minor) + $patch; $versstr = "$sshid $major.$minor.$patch"; $error = undef; last; @@ -393,7 +393,7 @@ sub sshversioninfo { $minor = $2; $patch = $4 ? $4 : 0; $sshid = 'OpenSSH-Windows'; - $versnum = (100*$major) + (10*$minor) + $patch; + $versnum = (100 * $major) + (10 * $minor) + $patch; $versstr = "$sshid $major.$minor.$patch"; $error = undef; last; @@ -403,7 +403,7 @@ sub sshversioninfo { $minor = $2; $patch = $4 ? $4 : 0; $sshid = 'SunSSH'; - $versnum = (100*$major) + (10*$minor) + $patch; + $versnum = (100 * $major) + (10 * $minor) + $patch; $versstr = "$sshid $major.$minor.$patch"; $error = undef; last; diff --git a/tests/tunit/CMakeLists.txt b/tests/tunit/CMakeLists.txt index c6ac279a660a..2e829c9a8ca0 100644 --- a/tests/tunit/CMakeLists.txt +++ b/tests/tunit/CMakeLists.txt @@ -45,7 +45,7 @@ target_include_directories(${BUNDLE} PRIVATE "${PROJECT_BINARY_DIR}/lib" # for "curl_config.h" "${PROJECT_SOURCE_DIR}/lib" # for "curl_setup.h", curlx "${PROJECT_SOURCE_DIR}/src" # for tool headers - "${PROJECT_SOURCE_DIR}/tests/libtest" # for "first.h", "unitcheck.h" + "${PROJECT_SOURCE_DIR}/tests/libtest" # for "first.c", "first.h", "unitcheck.h" "${CMAKE_CURRENT_SOURCE_DIR}" # for the generated bundle source to find included test sources ) target_compile_definitions(${BUNDLE} PRIVATE ${CURL_DEBUG_MACROS}) diff --git a/tests/unit/CMakeLists.txt b/tests/unit/CMakeLists.txt index 46710ce1eaf1..3c24940b5807 100644 --- a/tests/unit/CMakeLists.txt +++ b/tests/unit/CMakeLists.txt @@ -45,7 +45,7 @@ target_link_libraries(${BUNDLE} curlu) target_include_directories(${BUNDLE} PRIVATE "${PROJECT_BINARY_DIR}/lib" # for "curl_config.h", "unitprotos.h" "${PROJECT_SOURCE_DIR}/lib" # for "curl_setup.h", curlx - "${PROJECT_SOURCE_DIR}/tests/libtest" # for "first.h", "unitcheck.h" + "${PROJECT_SOURCE_DIR}/tests/libtest" # for "first.c", "first.h", "unitcheck.h" "${CMAKE_CURRENT_SOURCE_DIR}" # for the generated bundle source to find included test sources ) # unit tests are small pretend-libcurl-programs, pass BUILDING_LIBCURL to reflect that diff --git a/tests/unit/unit1397.c b/tests/unit/unit1397.c index ba11722bbf46..18a1a4468090 100644 --- a/tests/unit/unit1397.c +++ b/tests/unit/unit1397.c @@ -135,7 +135,7 @@ static CURLcode test_unit1397(const char *arg) curlx_free(pattern); } - /* Embedded NULs in the pattern should not be misinterpreted. */ + /* Embedded NUL bytes in the pattern should not be misinterpreted. */ if(Curl_cert_hostcheck("foo\0bar", 7, "foo", 3)) { curl_mfprintf(stderr, "foo\\0bar incorrectly matched foo\n"); unitfail++; From 2b348649c68c1cd8cc1d055487f2fb2b59a353cb Mon Sep 17 00:00:00 2001 From: Viktor Szakats Date: Tue, 13 Oct 2026 17:20:45 +0200 Subject: [PATCH 474/481] GHA/scorecard: disable the `cache-mode` key To fix this error in the 'Run analysis' step: ``` Wrote bundle to file /tmp/bundle2839271098 2026/10/13 15:08:12 error sending scorecard results to webapp: http response 400, status: 400 Bad Request, error: {"code":400,"message":"workflow verification failed: unable to parse github workflow: [:23:1: unexpected key \"cache-mode\" for \"workflow\" section. expected one of \"concurrency\", \"defaults\", \"env\", \"jobs\", \"name\", \"on\", \"permissions\", \"run-name\" [syntax-check]]"} ``` It's a new, but legit key. It seems the ossf/scorecard-action does not recognize it yet. Further, it seems it outsources this check (indirectly) to actionlint, which I recently dropped from CI because it's accumulating many fallouts due to the lack of upstream updates. It means the Scorecard analyzer may flag other similar ones that we previously explicitly silenced via the actionlint command-line. Refs: https://github.com/ossf/scorecard-action/blob/fa7117db83c52d787ae5a89f350999701505d78e/go.mod#L16 https://github.com/ossf/scorecard/issues/5278 Follow-up to e8ca163de2c705c416292ac6f044768c44e8b9ad #23387 Follow-up to 410e444495c9e95ca56af15d879df27d1358f572 #23194 Closes #23388 --- .github/workflows/scorecard.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index 1462d0885e01..bbac296e463a 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -20,7 +20,8 @@ concurrency: cancel-in-progress: true permissions: {} -cache-mode: none +# Key not supported by ossf/scorecard-action as of v2.5.0 +# cache-mode: none jobs: analysis: From 9a6d4ffc1b6c184fb2ec7c6e4879c835cb367689 Mon Sep 17 00:00:00 2001 From: Jay Satiro Date: Wed, 30 Sep 2026 18:41:21 -0400 Subject: [PATCH 475/481] Revert "setopt: clear the 'custom' CA booleans when set to NULL" - Do not clear the bool indicating custom choice when NULL is passed as the option value. This change affects how libcurl behaves when the NULL value is set for SSL CA options CURLOPT_CAINFO, CURLOPT_CAPATH, CURLOPT_CAINFO_BLOB, CURLOPT_PROXY_CAINFO, CURLOPT_PROXY_CAPATH, CURLOPT_PROXY_CAINFO_BLOB. Those options have a variable that tracks when they are set to determine whether or not to use a default value. Prior to this change, and since 5df33ef (precedes 8.22.0), if those options were set to NULL then the respective tracking variable would be set to false to indicate the user did not make a custom choice. Prior to 5df33ef, if those options were set to NULL then the respective tracking variable would be set to true to indicate the user did make a custom choice. This change effectively restores the behavior prior to 5df33ef because users may set the option value to NULL to indicate that they do not want the default value used. In other words, NULL is a valid custom choice and should not cause the option to revert to its default string. Bug: https://github.com/curl/curl/discussions/22614 Reported-by: tawmoto@users.noreply.github.com Closes https://github.com/curl/curl/pull/23202 --- docs/libcurl/opts/CURLOPT_PROXY_CAINFO.md | 3 +-- lib/setopt.c | 20 ++++++++++++-------- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/docs/libcurl/opts/CURLOPT_PROXY_CAINFO.md b/docs/libcurl/opts/CURLOPT_PROXY_CAINFO.md index 3eb5af912ac2..b9c8247e6a5d 100644 --- a/docs/libcurl/opts/CURLOPT_PROXY_CAINFO.md +++ b/docs/libcurl/opts/CURLOPT_PROXY_CAINFO.md @@ -51,8 +51,7 @@ The application does not have to keep the string around after setting this option. Using this option multiple times makes the last set string override the -previous ones. Set it to NULL to disable its use again and switches back to -internal default. +previous ones. Set it to NULL to disable its use again. The default value for this can be figured out with CURLINFO_CAINFO(3). diff --git a/lib/setopt.c b/lib/setopt.c index cbfeb7ca5f9e..dff8a6dc1298 100644 --- a/lib/setopt.c +++ b/lib/setopt.c @@ -1739,8 +1739,8 @@ static CURLcode setopt_cptr_proxy(struct Curl_easy *data, CURLoption option, * CA certificate */ result = Curl_setstropt(data, STRING_SSL_CAFILE_PROXY, ptr); - s->proxy_ssl.custom_cafile = - !!CURL_EASY_STR(data, STRING_SSL_CAFILE_PROXY); + if(!result) + s->proxy_ssl.custom_cafile = TRUE; return result; case CURLOPT_PROXY_CRLFILE: /* @@ -1766,8 +1766,8 @@ static CURLcode setopt_cptr_proxy(struct Curl_easy *data, CURLoption option, if(Curl_ssl_supports(data, SSLSUPP_CA_PATH)) { /* This does not work on Windows. */ result = Curl_setstropt(data, STRING_SSL_CAPATH_PROXY, ptr); - s->proxy_ssl.custom_capath = - !!CURL_EASY_STR(data, STRING_SSL_CAPATH_PROXY); + if(!result) + s->proxy_ssl.custom_capath = TRUE; return result; } #endif @@ -1883,7 +1883,8 @@ static CURLcode setopt_cptr_ssl(struct Curl_easy *data, CURLoption option, * Set CA info for SSL connection. Specify filename of the CA certificate */ result = Curl_setstropt(data, STRING_SSL_CAFILE, ptr); - data->set.ssl.custom_cafile = !!CURL_EASY_STR(data, STRING_SSL_CAFILE); + if(!result) + data->set.ssl.custom_cafile = TRUE; return result; case CURLOPT_CAPATH: /* @@ -1893,7 +1894,8 @@ static CURLcode setopt_cptr_ssl(struct Curl_easy *data, CURLoption option, if(Curl_ssl_supports(data, SSLSUPP_CA_PATH)) { /* This does not work on Windows. */ result = Curl_setstropt(data, STRING_SSL_CAPATH, ptr); - data->set.ssl.custom_capath = !!CURL_EASY_STR(data, STRING_SSL_CAPATH); + if(!result) + data->set.ssl.custom_capath = TRUE; return result; } return CURLE_NOT_BUILT_IN; @@ -2827,7 +2829,8 @@ static CURLcode setopt_blob(struct Curl_easy *data, CURLoption option, #ifdef USE_SSL if(Curl_ssl_supports(data, SSLSUPP_CAINFO_BLOB)) { CURLcode result = Curl_setblobopt(&s->blobs[BLOB_CAINFO_PROXY], blob); - s->proxy_ssl.custom_cablob = !!s->blobs[BLOB_CAINFO_PROXY]; + if(!result) + s->proxy_ssl.custom_cablob = TRUE; return result; } #endif @@ -2853,7 +2856,8 @@ static CURLcode setopt_blob(struct Curl_easy *data, CURLoption option, #ifdef USE_SSL if(Curl_ssl_supports(data, SSLSUPP_CAINFO_BLOB)) { CURLcode result = Curl_setblobopt(&s->blobs[BLOB_CAINFO], blob); - s->ssl.custom_cablob = !!s->blobs[BLOB_CAINFO]; + if(!result) + s->ssl.custom_cablob = TRUE; return result; } #endif From 978bfe9d02ce38fea62d973fc2b3c680bf8e635f Mon Sep 17 00:00:00 2001 From: David Bures Date: Sun, 11 Oct 2026 13:10:22 -0700 Subject: [PATCH 476/481] schannel: retain close_notify records across send retries Keep the SSPI shutdown token and its sent offset until the complete record has been written. Regenerating it after CURLE_AGAIN loses an encrypted record and can make the peer reject the next record. Handle short writes without advancing the TLS context again and free pending tokens when the connection closes. Add a native unit test for would-block, partial writes, pending-token cleanup and send failures. Prior to this change libcurl didn't properly handle the case where the close_notify TLS record could not be sent in full on the first try. Microsoft observed FTPS upload failures due to this problem. Refer to the pull request for more detail. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Closes https://github.com/curl/curl/pull/23368 --- lib/vtls/schannel.c | 80 ++++++++---- lib/vtls/schannel_int.h | 3 + tests/data/Makefile.am | 1 + tests/data/test4782 | 20 +++ tests/unit/Makefile.inc | 3 +- tests/unit/unit4782.c | 283 ++++++++++++++++++++++++++++++++++++++++ 6 files changed, 363 insertions(+), 27 deletions(-) create mode 100644 tests/data/test4782 create mode 100644 tests/unit/unit4782.c diff --git a/lib/vtls/schannel.c b/lib/vtls/schannel.c index 312d86ea3152..2832bb3a35e5 100644 --- a/lib/vtls/schannel.c +++ b/lib/vtls/schannel.c @@ -1056,6 +1056,9 @@ static CURLcode schannel_connect_step1(struct Curl_cfilter *cf, backend->recv_renegotiating = FALSE; backend->renegotiate_state.started = FALSE; backend->encdata_is_incomplete = FALSE; + backend->shutdown_generated = FALSE; + backend->shutdown_offset = 0; + memset(&backend->shutdown_token, 0, sizeof(backend->shutdown_token)); /* continue to second handshake step */ connssl->connecting_state = ssl_connect_2; @@ -2431,7 +2434,8 @@ static CURLcode schannel_shutdown(struct Curl_cfilter *cf, goto out; } - if(backend->cred && backend->ctxt && !backend->sent_shutdown) { + if(backend->cred && backend->ctxt && !backend->sent_shutdown && + !backend->shutdown_generated) { SecBufferDesc BuffDesc; SecBuffer Buffer; SECURITY_STATUS sspi_status; @@ -2471,41 +2475,60 @@ static CURLcode schannel_shutdown(struct Curl_cfilter *cf, &backend->ret_flags, NULL); if((sspi_status == SEC_E_OK) || (sspi_status == SEC_I_CONTEXT_EXPIRED)) { - /* send close message which is in output buffer */ - size_t written; + backend->shutdown_token = outbuf; + backend->shutdown_generated = TRUE; + } + else { + char buffer[STRERROR_LEN]; + if(outbuf.pvBuffer) + Curl_pSecFn->FreeContextBuffer(outbuf.pvBuffer); + failf(data, "schannel: generating close msg failed: %s", + Curl_sspi_strerror(sspi_status, buffer, sizeof(buffer))); + result = CURLE_SEND_ERROR; + goto out; + } + } + + if(backend->shutdown_generated && !backend->sent_shutdown) { + size_t written = 0; + size_t remaining; + + DEBUGASSERT(backend->shutdown_offset <= backend->shutdown_token.cbBuffer); + remaining = backend->shutdown_token.cbBuffer - backend->shutdown_offset; + if(remaining) { result = Curl_conn_cf_send(cf->next, data, - (const uint8_t *)outbuf.pvBuffer, - outbuf.cbBuffer, - FALSE, &written); - Curl_pSecFn->FreeContextBuffer(outbuf.pvBuffer); - if(!result) { - if(written < outbuf.cbBuffer) { - result = CURLE_SEND_ERROR; - failf(data, "schannel: failed to send close msg: %s" - " (bytes written: %zu)", curl_easy_strerror(result), written); - goto out; - } - backend->sent_shutdown = TRUE; - *done = TRUE; - } - else if(result == CURLE_AGAIN) { + ((uint8_t *)backend->shutdown_token.pvBuffer + + backend->shutdown_offset), + remaining, FALSE, &written); + if(!result) + backend->shutdown_offset += written; + if(result == CURLE_AGAIN || (!result && written < remaining)) { connssl->io_need = CURL_SSL_IO_NEED_SEND; result = CURLE_OK; goto out; } - else { - if(!backend->recv_connection_closed) { - result = CURLE_SEND_ERROR; - failf(data, "schannel: error sending close msg: %d", (int)result); - goto out; - } - /* Looks like server already closed the connection. - * An error to send our close notify is not a failure. */ + } + else /* no shutdown message to send? */ + result = CURLE_OK; + if(result) { + if(backend->recv_connection_closed) { + /* The server already closed, so sending close_notify is optional. */ *done = TRUE; result = CURLE_OK; } + else { + failf(data, "schannel: error sending close msg: %d", (int)result); + result = CURLE_SEND_ERROR; + } + goto out; } + if(backend->shutdown_token.pvBuffer) + Curl_pSecFn->FreeContextBuffer(backend->shutdown_token.pvBuffer); + memset(&backend->shutdown_token, 0, sizeof(backend->shutdown_token)); + backend->shutdown_offset = 0; + backend->sent_shutdown = TRUE; + *done = TRUE; } /* If the connection seems open and we have not seen the close notify @@ -2547,6 +2570,11 @@ static void schannel_close(struct Curl_cfilter *cf, struct Curl_easy *data) DEBUGASSERT(data); DEBUGASSERT(backend); + if(backend->shutdown_token.pvBuffer) + Curl_pSecFn->FreeContextBuffer(backend->shutdown_token.pvBuffer); + memset(&backend->shutdown_token, 0, sizeof(backend->shutdown_token)); + backend->shutdown_offset = 0; + /* free SSPI Schannel API security context handle */ if(backend->ctxt) { if(cf->conn->sslContext == &backend->ctxt->ctxt_handle) diff --git a/lib/vtls/schannel_int.h b/lib/vtls/schannel_int.h index fdf433fcff7c..99790b3778ed 100644 --- a/lib/vtls/schannel_int.h +++ b/lib/vtls/schannel_int.h @@ -118,6 +118,8 @@ struct schannel_ssl_backend_data { struct sbuffer decdata; struct Curl_schannel_cred *cred; struct Curl_schannel_ctxt *ctxt; + SecBuffer shutdown_token; + size_t shutdown_offset; SecPkgContext_StreamSizes stream_sizes; unsigned long req_flags, ret_flags; CURLcode recv_unrecoverable_err; /* schannel_recv had an unrecoverable err */ @@ -132,6 +134,7 @@ struct schannel_ssl_backend_data { BIT(use_alpn); /* true if ALPN is used for this connection */ BIT(use_manual_cred_validation); /* true if manual cred validation is used */ BIT(sent_shutdown); + BIT(shutdown_generated); /* encdata_is_incomplete: if encdata contains only a partial record that cannot be decrypted without another recv() (that is, status is SEC_E_INCOMPLETE_MESSAGE) then set this true. after recv() adds more diff --git a/tests/data/Makefile.am b/tests/data/Makefile.am index e02c1330acae..d066f2db583d 100644 --- a/tests/data/Makefile.am +++ b/tests/data/Makefile.am @@ -2146,6 +2146,7 @@ TESTCASES = \ test4656 \ test4657 \ test4781 \ + test4782 \ test5000 \ test5001 \ test5002 \ diff --git a/tests/data/test4782 b/tests/data/test4782 new file mode 100644 index 000000000000..dd44ac857785 --- /dev/null +++ b/tests/data/test4782 @@ -0,0 +1,20 @@ + + + + +unittest +Schannel +TLS + + + + + +unittest +Schannel + + +Schannel shutdown retries retain unsent TLS records + + + diff --git a/tests/unit/Makefile.inc b/tests/unit/Makefile.inc index e6ee7302b879..52296b056606 100644 --- a/tests/unit/Makefile.inc +++ b/tests/unit/Makefile.inc @@ -109,4 +109,5 @@ TESTS_C = \ unit3304.c \ unit3306.c \ unit3400.c \ - unit4781.c + unit4781.c \ + unit4782.c diff --git a/tests/unit/unit4782.c b/tests/unit/unit4782.c new file mode 100644 index 000000000000..fe0afc4fb5d7 --- /dev/null +++ b/tests/unit/unit4782.c @@ -0,0 +1,283 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) David Bures + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ +#include "unitcheck.h" + +#ifdef USE_SCHANNEL +#include "urldata.h" +#include "cfilters.h" +#include "curl_sspi.h" +#include "vtls/schannel.h" +#include "vtls/schannel_int.h" +#include "vtls/vtls_int.h" + +static const unsigned char t4782_token[] = { 1, 2, 3, 4, 5, 6, 7, 8 }; + +static struct { + unsigned int generated; + unsigned int freed; + unsigned int sends; + unsigned int blocks; + size_t accepted; + size_t first_write; + CURLcode send_error; + SECURITY_STATUS generate_error; +} t4782; + +static SECURITY_STATUS SEC_ENTRY t4782_apply(PCtxtHandle context, + PSecBufferDesc input_desc) +{ + (void)context; + fail_unless(input_desc->cBuffers == 1, "one shutdown input buffer"); + fail_unless(*(DWORD *)input_desc->pBuffers[0].pvBuffer == SCHANNEL_SHUTDOWN, + "shutdown token"); + return SEC_E_OK; +} + +static SECURITY_STATUS SEC_ENTRY t4782_generate( + PCredHandle credential, PCtxtHandle context, TCHAR *target, + unsigned long flags, unsigned long reserved, unsigned long representation, + PSecBufferDesc input_desc, unsigned long reserved2, PCtxtHandle new_context, + PSecBufferDesc output_desc, unsigned long *attributes, PTimeStamp expiry) +{ + SecBuffer *token = &output_desc->pBuffers[0]; + (void)credential; + (void)context; + (void)target; + (void)flags; + (void)reserved; + (void)representation; + (void)input_desc; + (void)reserved2; + (void)new_context; + (void)attributes; + (void)expiry; + t4782.generated++; + token->pvBuffer = curlx_malloc(sizeof(t4782_token)); + if(!token->pvBuffer) + return SEC_E_INSUFFICIENT_MEMORY; + memcpy(token->pvBuffer, t4782_token, sizeof(t4782_token)); + token->cbBuffer = sizeof(t4782_token); + token->BufferType = SECBUFFER_TOKEN; + return t4782.generate_error; +} + +static SECURITY_STATUS SEC_ENTRY t4782_free(void *buffer) +{ + t4782.freed++; + curlx_free(buffer); + return SEC_E_OK; +} + +static SECURITY_STATUS SEC_ENTRY t4782_delete(PCtxtHandle context) +{ + (void)context; + return SEC_E_OK; +} + +static SECURITY_STATUS SEC_ENTRY t4782_free_cred(PCredHandle credential) +{ + (void)credential; + return SEC_E_OK; +} + +static CURLcode t4782_send(struct Curl_cfilter *cf, struct Curl_easy *data, + const uint8_t *buffer, size_t length, bool eos, + size_t *written) +{ + (void)cf; + (void)data; + (void)eos; + t4782.sends++; + *written = 0; + fail_unless(length == sizeof(t4782_token) - t4782.accepted, + "retry begins at the unsent offset"); + verify_memory(buffer, t4782_token + t4782.accepted, (int)length); + if(t4782.blocks) { + t4782.blocks--; + return CURLE_AGAIN; + } + if(t4782.send_error) + return t4782.send_error; + *written = t4782.first_write ? t4782.first_write : length; + t4782.first_write = 0; + t4782.accepted += *written; + return CURLE_OK; +} + +static CURLcode t4782_recv(struct Curl_cfilter *cf, struct Curl_easy *data, + char *buffer, size_t length, size_t *received) +{ + (void)cf; + (void)data; + (void)buffer; + (void)length; + *received = 0; + return CURLE_AGAIN; +} + +static void t4782_destroy(struct Curl_cfilter *cf, struct Curl_easy *data) +{ + (void)cf; + (void)data; +} + +static const struct Curl_cftype t4782_filter = { + "TEST", + 0, + CURL_LOG_LVL_NONE, + t4782_destroy, + Curl_cf_def_connect, + Curl_cf_def_shutdown, + Curl_cf_def_adjust_pollset, + Curl_cf_def_data_pending, + t4782_send, + t4782_recv, + Curl_cf_def_cntrl, + Curl_cf_def_conn_is_alive, + Curl_cf_def_conn_keep_alive, + Curl_cf_def_query, +}; + +static CURLcode t4782_setup(CURL **easy) +{ + CURLcode result = CURLE_OK; + global_init(CURL_GLOBAL_ALL); + *easy = curl_easy_init(); + if(!*easy) { + curl_global_cleanup(); + return CURLE_OUT_OF_MEMORY; + } + return result; +} + +static void t4782_stop(CURL *easy) +{ + curl_easy_cleanup(easy); + curl_global_cleanup(); +} +#endif + +static CURLcode test_unit4782(const char *arg) +{ +#ifdef USE_SCHANNEL + CURL *easy = NULL; + PSecurityFunctionTable original; + SecurityFunctionTable functions; + static char hostname[] = "example.invalid"; + unsigned int scenario; + UNITTEST_BEGIN(t4782_setup(&easy)) + + original = Curl_pSecFn; + functions = *original; + functions.ApplyControlToken = t4782_apply; + functions.InitializeSecurityContext = t4782_generate; + functions.FreeContextBuffer = t4782_free; + functions.DeleteSecurityContext = t4782_delete; + functions.FreeCredentialsHandle = t4782_free_cred; + Curl_pSecFn = &functions; + + for(scenario = 0; scenario < 6; scenario++) { + struct schannel_ssl_backend_data backend; + struct ssl_connect_data ssl; + struct connectdata connection; + struct Curl_peer origin; + struct Curl_cfilter cf; + struct Curl_cfilter lower; + CURLcode result; + bool done; + unsigned int attempts; + + memset(&t4782, 0, sizeof(t4782)); + memset(&backend, 0, sizeof(backend)); + memset(&ssl, 0, sizeof(ssl)); + memset(&connection, 0, sizeof(connection)); + memset(&origin, 0, sizeof(origin)); + memset(&cf, 0, sizeof(cf)); + memset(&lower, 0, sizeof(lower)); + backend.cred = curlx_calloc(1, sizeof(*backend.cred)); + backend.ctxt = curlx_calloc(1, sizeof(*backend.ctxt)); + if(!backend.cred || !backend.ctxt) { + curlx_free(backend.cred); + curlx_free(backend.ctxt); + fail("allocation failed"); + break; + } + backend.cred->refcount = 1; + backend.recv_sspi_close_notify = TRUE; + ssl.backend = &backend; + ssl.peer.origin = &origin; + origin.hostname = hostname; + origin.port = 443; + cf.ctx = &ssl; + cf.conn = &connection; + cf.next = &lower; + lower.cft = &t4782_filter; + + if(scenario == 0 || scenario == 2) + t4782.blocks = 2; + if(scenario == 1) + t4782.first_write = 1; + if(scenario == 3 || scenario == 4) + t4782.send_error = CURLE_SEND_ERROR; + if(scenario == 4) + backend.recv_connection_closed = TRUE; + if(scenario == 5) + t4782.generate_error = SEC_E_INTERNAL_ERROR; + + result = Curl_ssl_schannel.shut_down(&cf, easy, TRUE, &done); + if(scenario < 3) { + fail_unless(result == CURLE_OK && !done && !cf.shutdown, + "incomplete send keeps shutdown pending"); + fail_unless(t4782.generated == 1 && t4782.freed == 0, + "pending token remains owned"); + fail_unless(ssl.io_need == CURL_SSL_IO_NEED_SEND, + "pending shutdown waits for writability"); + if(scenario != 2) { + for(attempts = 0; !done && attempts < 3; attempts++) + result = Curl_ssl_schannel.shut_down(&cf, easy, TRUE, &done); + fail_unless(result == CURLE_OK && done && cf.shutdown, + "shutdown completed"); + fail_unless(t4782.generated == 1 && t4782.freed == 1, + "retry reuses and then frees one token"); + fail_unless(t4782.accepted == sizeof(t4782_token), + "complete record delivered once"); + } + } + else { + fail_unless(result == (scenario == 4 ? CURLE_OK : CURLE_SEND_ERROR), + "send/generation failure retains existing error semantics"); + fail_unless(cf.shutdown, "failed shutdown does not retry"); + } + Curl_ssl_schannel.close(&cf, easy); + fail_unless(t4782.generated == 1 && t4782.freed == 1, + "close releases the token exactly once"); + } + + Curl_pSecFn = original; + UNITTEST_END(t4782_stop(easy)) +#else + (void)arg; + return CURLE_OK; +#endif +} From e6dec1fc5f4f60430b23069e690820d2b6524e63 Mon Sep 17 00:00:00 2001 From: dataisland Date: Sat, 10 Oct 2026 22:59:12 -0700 Subject: [PATCH 477/481] dnscache: compare full hostnames when lookup keys are truncated - Require that both hostnames for comparison fit the key before using the fast path; otherwise compare the complete case-insensitive hostname. - Extend test unit3271 with both length orderings and equal long names. Prior to this change the check was incomplete and the comparison could be erroneously skipped. Follow-up to 62a6ee5. Closes https://github.com/curl/curl/pull/23350 --- lib/vdns/dnscache.c | 12 ++++++++---- tests/unit/unit3271.c | 30 ++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 4 deletions(-) diff --git a/lib/vdns/dnscache.c b/lib/vdns/dnscache.c index f7e6ad5cfcf9..303e11b18c05 100644 --- a/lib/vdns/dnscache.c +++ b/lib/vdns/dnscache.c @@ -97,12 +97,16 @@ UNITTEST void dnsc_id2key(struct dnsc_key *key, struct dnsc_id *id) key->len = namelen + 3; } -static bool dnsc_id_matches_entry(struct dnsc_id *id, - struct Curl_dns_entry *dns) +/* @unittest 3271 */ +UNITTEST bool dnsc_id_matches_entry(struct dnsc_id *id, + struct Curl_dns_entry *dns); +UNITTEST bool dnsc_id_matches_entry(struct dnsc_id *id, + struct Curl_dns_entry *dns) { return dns && - /* hash key was not truncated OR */ - ((dns->hostlen <= (MAX_HOSTCACHE_LEN - 3)) || + /* Neither the requested nor the cached key was truncated. */ + (((dns->hostlen <= (MAX_HOSTCACHE_LEN - 3)) && + (id->name.len <= (MAX_HOSTCACHE_LEN - 3))) || /* port and host string are the same, case insensitive */ ((id->port == dns->port) && (id->name.len == dns->hostlen) && /* for most lookups, case is the same, try that first */ diff --git a/tests/unit/unit3271.c b/tests/unit/unit3271.c index c08a7c92e6a6..abab4cc700f3 100644 --- a/tests/unit/unit3271.c +++ b/tests/unit/unit3271.c @@ -96,6 +96,36 @@ static CURLcode test_unit3271(const char *arg) struct Curl_multi *multi = NULL; struct Curl_easy *easy = NULL; struct curl_slist *list = NULL; + char hostname[258]; + struct dnsc_id long_id; + struct Curl_dns_entry *long_dns; + + /* Check full hostname matching when the DNS cache key is truncated. */ + memset(hostname, 'a', sizeof(hostname) - 1); + hostname[sizeof(hostname) - 1] = 0; + long_dns = curlx_calloc(1, sizeof(*long_dns) + sizeof(hostname)); + abort_unless(long_dns, "could not allocate cache entry"); + hostname[255] = 0; + memcpy(long_dns->hostname, hostname, 256); + hostname[255] = 'a'; + long_dns->hostlen = 255; + long_dns->port = 80; + long_id.port = 80; + curlx_str_assign(&long_id.name, hostname, strlen(hostname)); + fail_unless(!dnsc_id_matches_entry(&long_id, long_dns), + "long query must not match shorter cached hostname"); + memcpy(long_dns->hostname, hostname, sizeof(hostname)); + long_dns->hostlen = (uint16_t)strlen(hostname); + fail_unless(dnsc_id_matches_entry(&long_id, long_dns), + "identical long hostname must match"); + hostname[0] = 'A'; + fail_unless(dnsc_id_matches_entry(&long_id, long_dns), + "long hostname lookup must remain case insensitive"); + hostname[255] = 0; + curlx_str_assign(&long_id.name, hostname, strlen(hostname)); + fail_unless(!dnsc_id_matches_entry(&long_id, long_dns), + "short query must not match longer cached hostname"); + curlx_free(long_dns); /* important: we setup cache outside of the loop and also clean cache after the loop. In contrast, for example, test 1607 sets up and cleans cache on From bcf2691665b5516ae1211ba27d431ff7c3bb6fcd Mon Sep 17 00:00:00 2001 From: Daniel Stenberg Date: Wed, 14 Oct 2026 07:53:12 +0200 Subject: [PATCH 478/481] THANKS: add 8.23.0 contributors --- docs/THANKS | 58 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/docs/THANKS b/docs/THANKS index 36a75f3de390..0fb35227178d 100644 --- a/docs/THANKS +++ b/docs/THANKS @@ -44,6 +44,7 @@ Adam Piggott Adam Rosenfield Adam Sampson adamse on github +Adam Shirt Adam Tkac ad-chaos on github Aditya Garg @@ -54,6 +55,7 @@ Adrian Burcea Adriano Meirelles Adrian Peniak Adrian Schuur +Adrien Rey-Jarthon Ady Elouej afengsoft on github afrind on github @@ -108,6 +110,7 @@ Alexander Elgert Alexander Jaeger Alexander Kanavin Alexander Klauer +Alexander Köplinger Alexander Kourakos Alexander Krasnostavsky Alexander Lazic @@ -280,6 +283,7 @@ Anthony G. Basile Anthony Hu Anthony Ramine Anthony Shaw +Anthropic Antoine Aubert Antoine Bollengier Antoine Calando @@ -321,12 +325,14 @@ Arne Soete Aron Bergman Aron Rotteveel Artak Galoyan +Artem Prilutskiy Arthur Murray Artur Sinila Arve Knudsen Arvid Norberg arvids-kokins-bidstack on github asavah on github +aschank on github Asger Hautop Drewsen Ashish Shukla Ashwin Metpalli @@ -343,9 +349,11 @@ Austin Green Austin Moore av223119 on github Avery Fay +Aviv Engelberg awesomekosm on github awesomenode on github Axel Chong +Axel Mierczuk Axel Morawietz Axel Tillequin Ayesh Karunaratne @@ -451,6 +459,7 @@ blankie bnfp on github bo0tzz on github Bo Anderson +Bobbeh Rhino bobmitchell1956 on github BobodevMm on github Bob Relyea @@ -524,6 +533,7 @@ bsr13 on hackerone bttrfl on github bubbleguuum on github Bubu on github +bupt-Yy-young on github buzo-ffm on github bxac on github Bylon2 on github @@ -575,6 +585,7 @@ Chandrakant Bagul Chara White Charles Cazabon Charles Kerr +Charles Muehlberger Charles Romestant Charlie C chemodax @@ -751,6 +762,7 @@ Daniel Kahn Gillmor Daniel Katz Daniel Krügler Daniel Kurečka +Daniel Lang Daniel Lee Hwang Daniel Lublin Daniel Marjamäki @@ -765,6 +777,7 @@ Daniel Schulte Daniel Seither Daniel Shahaf Daniel Silverstone +Daniel S. Roche Daniel Steinberg Daniel Stenberg Daniel Szmulewicz @@ -794,6 +807,7 @@ Darryl House Darshan Mody dasimx on github DasKutti on github +dataisland Dave Cottlehuber Dave Dribin Dave Halbakken @@ -810,6 +824,7 @@ David Benjamin David Binderman David Blaikie David Bohman +David Bures David Byron David Carlier David Cohen @@ -1150,10 +1165,13 @@ Felix Kaiser Felix von Leitner Felix Yan Feng Tu +Fengxiaoxx on github +Fernando Gallardo Fernando Muñoz ffath-vo on github Filipe Casal Filip Lundgren +Filippo Tedeschi Filip Salomonsson finkjsc on github Fiona Klute @@ -1164,6 +1182,7 @@ fjaell on github Flameborn on github Flavio Amieiro Flavio Medeiros +Florent Castelli Florian Eckert Florian Friedrich Florian Imdahl @@ -1201,6 +1220,7 @@ Fred Klassen Fred Machado Fred New Fred Noz +Fredrik Blau Fredrik Thulin Fred Stluka FuccDucc on github @@ -1285,6 +1305,7 @@ Gorilla Maguila Goro FUJI Götz Babin-Ebell Gou Lingfeng +GPT-6.1 Sol Graham Campbell Graham Christensen Grant Erickson @@ -1358,6 +1379,7 @@ Harry Mallon Harry Sarson Harry Sintonen Harshal Pradhan +Hasan Flayyih Abdullah Hauke Duden Haydar Alaidrus Hayden Roche @@ -1407,6 +1429,7 @@ HsiehYuho on github htasta htasta on github huanghuihui0904 +Huang Yiheng Hubert Kario Hugh Macdonald Hugo van Kemenade @@ -1446,11 +1469,13 @@ ihsinme on github Iida Yosiaki Ikko Ashimine Ilguiz Latypov +Ilias Aberkane Ilja van Sprundel Illarion Taev illusory-dream on github Ilmari Lauhakangas Ilya Kosarev +İlyas Serdar Dursun imilli on github Immanuel Gregoire ImpatientHippo on github @@ -1550,6 +1575,7 @@ Jan Venekamp Jan Verbeek Jared Jennings Jared Lundell +jared-m-cohen-civ on github Jari Aalto Jari Sundell jasal82 on github @@ -1577,6 +1603,7 @@ Jaz Fresh JazJas on github jbgoog on github J. Bromley +jdymitarai Jean-Christophe Amiel Jean-Claude Chauve Jean Fabrice @@ -1780,6 +1807,7 @@ Jon Wilkes Joombalaya on github Joonas Kuorilehto Jordan Brown +Jorge Rocamora Jörg Mueller-Tolk Jörn Hartroth Jose Alf @@ -1808,6 +1836,7 @@ Josue Andrade Gomes Jozef Kralik Juan Barreto Juan Belón +JuanCarlosgg on github Juan Cruz Viotti Juan F. Codagnone Juan Ignacio Hervás @@ -1880,6 +1909,7 @@ Kartik Mahajan Kaspar Brand Katie Wang Katsuhiko YOSHIDA +KayanoLiam on github kayrus on github Kazuho Oku kchow-FTNT on github @@ -1964,6 +1994,7 @@ kuchara on github Kunal Chandarana Kunal Ekawde kupavcevdenis on github +Kuroda Kayn Kurt Fankhauser Kushal Das Kvarec Lezki @@ -2007,6 +2038,7 @@ Lauri Kasanen Lawrence Gripper Lawrence Matthews Lawrence Wagerfield +ldm0 Leah Neukirchen Lealem Amedie Leandro Coutinho @@ -2098,6 +2130,7 @@ Luke Granger-Brown Luke Hamburg Luke Wilde luminixinc on github +Luna Tong Luo Jinghua Luong Dinh Dung Luz Paz @@ -2118,6 +2151,7 @@ MaeIsBad on github magisterquis on hackerone Mahmoud Samir Fayed Maksim Arhipov +Maksim Hayder Maksim Kuzevanov Maksim Ściepanienka Maksim Stsepanenka @@ -2390,6 +2424,7 @@ Michel Promonet Miguel Angel Miguel Diaz migueljcrum on github +Mihai-Gabriel Marin Mihai Ionescu mik Mikael Johansson @@ -2419,6 +2454,7 @@ Milon Renatus Miloš Ljumović Mingliang Zhu Mingtao Yang +minnnjuuu Miroslav Franc Miroslav Spousta Mischa Salle @@ -2432,6 +2468,7 @@ Mohamed Osama Mohammad AlSaleh Mohammad Hasbini Mohammadreza Hendiani +Mohammed K. Fathy Mohammed Naser Mohammed Sadiq Mohun Biswas @@ -2570,6 +2607,7 @@ Norbert Kett norbertmm on github Norbert Novotny nosajsnikta on github +ns-ylambert NTMan on github Nuno Goncalves Nuru on github @@ -2631,6 +2669,7 @@ Ozan Cansel Pablo Busse Palo Markovic pandada8 on github +Paolo Ganci Paolo Mossino Paolo Piacentini parasol-aser @@ -2870,6 +2909,7 @@ Rasmus Melchior Jacobsen Rasmus Thomsen Raul Onitza-Klugman Ravi Pratap +rawsun007 Ray Dassen Raymond Steen Ray Pekowski @@ -2885,8 +2925,10 @@ Reinout van Schouwen RekGRpth on github Remco van Hooff Remi Gacogne +Rémi NICOLE Remo E Rémy Léone +Renan Pimentel Renato Botelho Renaud Allard Renaud Chaillat @@ -2901,6 +2943,7 @@ RepoRascal on hackerone Reuven Wachtfogel RevaliQaQ on github Reza Arbab +Rhino Rianov Viacheslav riastradh on github Ricardo Cadime @@ -2923,6 +2966,7 @@ Richard Levitte Richard Marion Richard Michael Richard Moore +Richard Payne Richard Prescott Richard Silverman Richard Tollerton @@ -3028,6 +3072,7 @@ Ronnie Mose Ron Parker Ron Zapp Rosen Penev +Roshan Ramani Rosimildo da Silva Ross Burton roughtex on github @@ -3036,6 +3081,7 @@ Roy Li Roy Shan RubisetCie on github Rudi Heitbaum +Rudolf Polzer Rui LIU Rui Pinheiro Rune Kleveland @@ -3137,6 +3183,7 @@ Sebastian Haglund Sebastian Mundry Sebastian Neubauer Sebastian Pohlschmidt +Sebastian Raase Sebastian Rasmussen Sebastian Sterk Sebastian Walz @@ -3217,6 +3264,7 @@ Simon Warta simplerobot on github Sinkevich Artem Siva Sivaraman +Skye Soss Slaven Rezić SLDiggie on github Smackd0wn on github @@ -3295,6 +3343,7 @@ Stephen Farrell Stephen Kick Stephen M. Coakley Stephen More +Stephen Psaradellis Stephen Toub Sterling Hughes Steve Green @@ -3340,6 +3389,7 @@ swalkaus at yahoo.com sylgal on github Sylvestre Ledru Symeon Paraschoudis +szedenik-adam on github T200proX7 on github Tadej Vengust Tae Hyoung Ahn @@ -3367,6 +3417,7 @@ Terri Oda Terry Wu Tetetest thanhchungbtc on github +Thanniru Sai Teja TheAssassin on github TheBitBrine The Infinnovation team @@ -3443,6 +3494,7 @@ Tim Verhoeven Tim Yuer Tinus van den Berg tinyboxvk +Tip ten Brink tiymat TJ Saunders Tk Xiong @@ -3544,12 +3596,14 @@ Tupone Alfredo Turiiya T. Yamada Tyler Hall +Tyler Yankee u20221022 on github Ulf Härnhammar Ulf Samuelsson Ulrich Doehner Ulrich Telle Ulrich Zadow +Un1q32 UnicornZhang on github updatede on github UrsusArctos on github @@ -3666,6 +3720,7 @@ wncboy on github Wojciech Zwiefka wolfsage on hackerone Wolf Vollprecht +Wonyoung Jung Wouter Van Rooy wulin-nudt on github Wu Yongzheng @@ -3680,6 +3735,7 @@ Xavier Bouchoux XCas13 xfangfang XhmikosR on github +xhon-pelushi XhstormR on github xiadnoring on github Xiangbin Li @@ -3708,6 +3764,7 @@ yaoy6 on github Yarram Sunil Yasuharu Yamada Yasuhiro Matsumoto +Yechan Bae Yechiel Kalmenson Yedaya Katsman Yehezkel Horowitz @@ -3737,6 +3794,7 @@ Yuriy Chernyshov Yuriy Sosov yushicheng7788 on github Yusuke Nakamura +yuwk Yu Xin Yuyi Wang Yves Arrouye From 3163ec065643ba20709f190111f42c3e1fe95273 Mon Sep 17 00:00:00 2001 From: Daniel Stenberg Date: Wed, 14 Oct 2026 07:51:16 +0200 Subject: [PATCH 479/481] RELEASE-NOTES: synced --- RELEASE-NOTES | 136 +++++++++++++++++++++++++++++++++++++++------ docs/THANKS-filter | 2 + 2 files changed, 120 insertions(+), 18 deletions(-) diff --git a/RELEASE-NOTES b/RELEASE-NOTES index 7d4a7b3cdd8d..2ef30ed91d96 100644 --- a/RELEASE-NOTES +++ b/RELEASE-NOTES @@ -4,8 +4,8 @@ curl and libcurl 8.23.0 Command line options: 278 curl_easy_setopt() options: 312 Public functions in libcurl: 100 - Authors: 1538 - Contributors: 3835 + Authors: 1547 + Contributors: 3844 This release includes the following changes: @@ -27,20 +27,26 @@ This release includes the following bugfixes: o capsule: reject a capsule that can never be buffered [84] o cf-h1-proxy: reset chunk state on new request [146] o cf-h1-proxy: stricter status line parsing [145] + o cf-https-connect: re-arm the timer for the hard eyeballs timeout [222] o cf-ngtcp2-cmn: fix possible `-Wdeclaration-after-statement` [296] o cf-ngtcp2-proxy: check for stream close on rx win update [159] o cf-ngtcp2-proxy: fix rx flow control [174] o cf-socket: adjust NO_SYN_RETRANSMISSIONS for older Windows [275] o cf-socket: clamp keepalive seconds-to-milliseconds conversions [167] + o cfilters: fail a send that reports more written than requested [194] o clang-tidy: merge nested `if()`s [39] o cmake/Find*: drop deprecated compatibility config variables [259] o cmake: enable C++ library for GCC in AWS-LC/BoringSSL static builds [232] o cmake: link llvm stdc++ lib to AWS-LC/BoringSSL-specific feature detections only [271] o config2setopts: always set the security options for SCP/SFTP [67] + o config2setopts: rename define for maximum cookie header length [248] o conncache: avoid evicting connect-only connections [79] o conncache: never discard a connection still in use [208] o conncache: remove bundle dest [50] + o connect: do not busy-loop in blocking connect without a timeout [240] + o connect: keep IP info and connect time when connect fails [195] o connectdata: use less curltime [34] + o cookie: accept and ignore empty property names in Set-Cookie [234] o cookie: accept and ignore values for 'secure' and 'httponly' [187] o cookie: accept slightly longer lines in cookie jars [105] o cookie: cookies for a PSL domain from jar just limits tailmatch [176] @@ -73,15 +79,19 @@ This release includes the following bugfixes: o digest: quote the digest-uri param as well [85] o dnscache/conncache tweaks [75] o dnscache: check hostname on lookup [209] + o dnscache: compare full hostnames when lookup keys are truncated [301] o docs: add the missing .html to the url-encode book link [3] o docs: document and align the blocking situations [163] + o docs: note CAINFO/SSL_VERIFYPEER/SSL_VERIFYSTATUS effect on LDAP [188] o docs: SSL session import/file updates [199] o doh: reject undersized HTTPS RDATA [11] o dynhds: check size overflows [198] o dynhds: grow header array geometrically [73] + o easy: change event pollset construction [242] o easy: duplicating a zero byte memory area needs no malloc [91] o easy: make ca_cache_timeout standalone [51] o easy: validate buffer pointer in curl_easy_recv and curl_easy_send [152] + o ECH: rework config, add test cases [220] o escape: polish encoding and decoding for speed [37] o escape: use hex-pair lookup in curl_easy_escape [77] o examples: clean up crawler link parsing resources [119] @@ -98,7 +108,10 @@ This release includes the following bugfixes: o getparam: switch off CONNECT headers when using -J [4] o glob: reduce the number of allocations for URLs that don't need globbing. [109] o global_init: tidy up, simplify, fix issues [43] + o gnutls: hardening [230] + o GnuTLS: remove our own OCSP signature verification [266] o gtls: no early return on verify fail [149] + o h3-proxy: teardown fix [182] o happy eyeballs: bound work from many duplicate/failing addresses [80] o hash: change key arguments to const [14] o hash: vary bucket placement across process runs [108] @@ -113,15 +126,19 @@ This release includes the following bugfixes: o http2: initialize URL handle for server pushes [104] o http2: limit trailers and headers count [211] o http2: update a refused stream's error after GOAWAY [102] + o http2: use stream in userdata [246] o http: abide to close-rules when transfer-encoding off [132] o http: ignore Proxy-Authenticate unless the response comes from a proxy [120] o http: only enable Negotiate/NTLM to allowed origins [99] + o http: reject empty header names [241] o http: reject resumed uploads [16] o http_aws_sigv4: fix the s3express service name [273] + o http_chunks: accept exactly 17 characters chunk-size [170] o http_chunks: limit trailers size [212] o httpsig-key.md: correct the generating Ed25519 keys commands [97] o httpsrr+ech: check target and port [133] o if2ip: check interface name case sensitive [158] + o imap: close the connection on premature done instead of finalizing [197] o imap: compare selected mailbox names case-sensitively [57] o imap: do size checks using 64-bit math [291] o inet_pton: reject a colon that ends an IPv6 address [179] @@ -129,6 +146,8 @@ This release includes the following bugfixes: o KNOWN_BUGS: drop the curl-config private details entry [290] o KNOWN_BUGS: drop the empty error buffer entry, it no longer happens [66] o ldap: base64-encode a non-SAFE DN and reject a control-byte type name [55] + o ldap: tidy up variable scope, merge nested `#if`s [206] + o lib557: exclude three floating point test cases for VS2013 and earlier [249] o lib: connection shutdown, cleanup code [92] o lib: make private functions static and remove unused code [83] o lib: reject internal handles in the multi/easy public API [103] @@ -137,19 +156,24 @@ This release includes the following bugfixes: o libssh2: skip repeat checkp() for already-absent hashed key types [82] o libssh: work with empty priv_key [143] o mbedtls: fix ticket handling [52] + o mbedtls: pass correct length for PEM/DER key blobs [177] o mbedtls: resend length check [140] o mdlinkcheck: pass `--retry-max-time` to avoid server-controlled long retry times [161] o mime: bound Curl_mime_duppart() recursion depth [164] o mime: bound Curl_mime_prepare_headers() recursion depth [76] o mime: do curl_mime_free() non-recursively [48] + o mprintf: acknowledge %F [300] o mprintf: avoid scanning strings twice for unbounded %s [110] o mprintf: make mprintf() and mfprintf() return -1 on error [279] o mprintf: stage output and emit it in runs [38] o mprintf: use size_t width for the bare Windows %I modifier [178] o mqtt: drain queued output before advancing the state machine [118] o mqtt: remaininglength check for 32-bit systems [150] + o mqttd: work around bogus VS2019 32-bit compiler warning [202] o mulit_ev: remove socket references on forget [89] o multi-event: fix timeout of pending transfers [111] + o multi: adjust the cleanup order in multi_done [135] + o multi: is_finished() fix [239] o multi: reduce struct size with small cleanups [25] o multi: remove redundant completion message list [32] o multi: store timeout epoch as whole seconds [36] @@ -164,6 +188,8 @@ This release includes the following bugfixes: o openldap: hand it a dup'd socket, not curl's own o openssl/gtls: reject CRLfile with native CA store, matching rustls [27] o openssl: fix OpenSSL v4.1 no-deprecated build [190] + o openssl: hardening [227] + o openssl: support X509 store caching when CApath is configured [264] o openssl: use non-deprecated API for MD5/MD4 [69] o os400: document curl_formadd_CCSID multi-chunk peculiarity [5] o os400: restore HAVE_FCHMOD [181] @@ -178,21 +204,32 @@ This release includes the following bugfixes: o peer: preserve ipv6 props from "connect-to" [155] o perf-tests: output two-digital decimals always [96] o pingpong: add a max loop counter for Curl_pp_readresp [113] + o pingpong: guard sendleft against a send reporting more than requested [196] + o pingpong: schedule server response timeouts for the socket API [221] + o pytest: adjust threshold in test_20_11_crazy_pings for arm64 [237] o pytest: check server status without ssl verify [154] + o pytest: fixture fixes [193] o pytest: set `PerSourcePenalties no` when sshd supports it [169] o pytest: silence pytest warnings [261] + o pytest: start all servers early [214] o retry.md: clarify that the server can override retry delay time [184] + o Revert "setopt: clear the 'custom' CA booleans when set to NULL" [235] o rtsp: accept a Content-Length body on methods that expected none [115] o rtsp: do not send a learned session id to another origin [10] o runtests: add extra room for mininum number of tests in sliced jobs [201] o runtests: Added runnernums in order to have O(1) lookup [45] + o runtests: unset `CURL_MEMDEBUG` for curl requests made to the CI API [258] o rustls: break recv loop on empty plain text [298] + o rustls: consume CURLOPT_SSLCERT_BLOB / CURLOPT_SSLKEY_BLOB [219] o schannel: auto_client_cert conn matching [17] o schannel: clear borrowed sslContext on close [15] o schannel: fix `-Wextra-semi-stmt` with clang and extra debug enabled [267] o schannel: fix compiler warning `-Wstrict-aliasing` in ALPN code [243] + o schannel: fix guard for old MSVC workaround [204] o schannel: handle empty tokens during renegotiation [183] + o schannel: retain close_notify records across send retries [302] o sectrust: add guards for rest of Apple OS flavors [250] + o share link: forget connection [223] o socketpair: fix accept loop [210] o socketpair: use `pipe2()` on Apple OSes (requires SDK 27) [262] o socks: support CURLAUTH_ONLY in CURLOPT_SOCKS5_AUTH [124] @@ -205,13 +242,17 @@ This release includes the following bugfixes: o telnet: make it work through proxy tunnels [112] o test 1605: move from unit to libtest [49] o test/servers.pm: restrict h2/3 port reuse [203] + o test1119, 1167: disable MSYS2 argument conversion [254] o test1399: improve reliability [9] o test1679: unset `SSL_CERT_FILE`, use `TESTNUMBER` [58] o test2007: bump to use SHA256 public key hash (was: MD5) [226] o test2007: set `MSYS2_ARG_CONV_EXCL` to avoid flaky failures [216] o test2072: enable on Cygwin/MSYS2 [256] + o test428: skip when running on older MSYS2 versions [276] o test: restore the old test 1609 as 3271 [162] + o testcurl.pl: drop support for building c-ares from embedded source tree [253] o tests/http: use @classmethod when appropriate [231] + o tests: add SFTP connection reuse tests [224] o tests: clean up Python test code [130] o tests: fix typos in a comment [247] o tests: require valgrind 3+ (2007-08-03) [282] @@ -219,9 +260,11 @@ This release includes the following bugfixes: o tool: (re)set SSL verification in --libcurl output [1] o tool: add Unicode and long-filename support to `--create-dir` (Windows) [141] o tool: buffer headers before remote filename selection [98] + o tool: handling of "synthetic" errors [233] o tool_doswin: recognize superscript numbers as reserved [117] o tool_easysrc: reset the handle between --next operations [90] o tool_getparam: do not print the value of a redacted argument [127] + o tool_getparam: restore --socks5-gssapi-service [200] o tool_help: guard category[2] access when category is bare "-" [59] o tool_ipfs: keep the path percent-encoded when rewriting to the gateway [138] o tool_operate: clear the retry flag when the output file fails [122] @@ -229,7 +272,10 @@ This release includes the following bugfixes: o u8_strset, u32_ptrset: fix boundaries handling [131] o uint-bset: skip unused slots when counting o uint-table: add remove-max precaution [165] + o unit1399: check microsecond precision across redirects [225] + o unit1664: add workaround for VS2019 16.3 - VS2022 17.4 x64 miscompile [272] o url: check syntax before applying default-protocol [147] + o url: CURLOPT_INTERFACE fix connection reuse match [186] o url: match connections without force reuse and candidates [95] o url: ntlm/negotiate, prohibit connection reuse on follows [18] o urlapi: make curl_url_dup() copy the guessed_scheme flag [166] @@ -246,6 +292,9 @@ This release includes the following bugfixes: o vtls: fix Curl_cert_hostcheck when the pattern is not a C string [12] o vtls_scache: remember lock state locally [297] o warnless: dedupe Intel compiler warning suppression pragmas [42] + o wcurl: import v2026.10.12 [236] + o websocket: auto PONG with client reader fix [215] + o websocket: error on EOS with incomplete frame [244] o wolfssl: remove BIO destroy callback [157] o ws: check pointer args in curl_ws_recv [192] o ws: do not auto-pong in raw mode [123] @@ -271,24 +320,26 @@ Planned upcoming removals include: This release would not have looked like this without help, code, reports and advice from friends like these: - 1rhino2 on hackerone, Adam Shirt, Alexander Köplinger, Andreas Westin, - Artem Prilutskiy, aschank on github, Aviv Engelberg, Axel Mierczuk, - Bobbeh Rhino, bupt-Yy-young on github, Charles Muehlberger, Christian Hesse, - Christian Ullrich, Claude, Dan Fandrich, Daniel Lang, Daniel McCarney, - Daniel S. Roche, Daniel Stenberg, David Benjamin, dependabot[bot], + 1rhino2 on hackerone, Adam Shirt, Adrien Rey-Jarthon, Alexander Köplinger, + Andreas Westin, Anthropic, Artem Prilutskiy, aschank on github, + Aviv Engelberg, Axel Mierczuk, Bobbeh Rhino, bupt-Yy-young on github, + Charles Muehlberger, Christian Hesse, Christian Ullrich, Dan Fandrich, + Daniel Lang, Daniel McCarney, Daniel S. Roche, Daniel Stenberg, Daniel Wade, + dataisland, David Benjamin, David Bures, dependabot[bot], Fengxiaoxx on github, Fernando Gallardo, Filippo Tedeschi, Florent Castelli, - Fredrik Blau, GPT-6.1 Sol, Huang Yiheng, Ilias Aberkane, - jared-m-cohen-civ on github, Johannes Schindelin, Jorge Rocamora, - Joshua Rogers, JuanCarlosgg on github, KayanoLiam on github, ldm0, Luna Tong, + Fredrik Blau, GPT-6.1 Sol, Hasan Flayyih Abdullah, Huang Yiheng, + Ilias Aberkane, İlyas Serdar Dursun, jared-m-cohen-civ on github, jdymitarai, + Johannes Schindelin, Jorge Rocamora, Joshua Rogers, JuanCarlosgg on github, + KayanoLiam on github, Kuroda Kayn, ldm0, Luna Tong, Maksim Hayder, Marcel Raad, Martin Dukek, Max Dymond, Mihai-Gabriel Marin, minnnjuuu, - Mohammed K. Fathy, Muhamad Arga Reksapati, OSS-Fuzz, Paolo Ganci, - Patrick Monnerat, rawsun007, Ray Satiro, Rémi NICOLE, renovate[bot], Rhino, - Richard Payne, Ron Kuper, Roshan Ramani, Rudolf Polzer, Samuel Henrique, - Sebastian Raase, Skye Soss, Stanislav Fort, Stefan Eissing, - Stephen Psaradellis, szedenik-adam on github, Thanniru Sai Teja, - Tip ten Brink, Tyler Yankee, Un1q32, Viktor Szakats, Wonyoung Jung, - xhon-pelushi, Yechan Bae, yuwk - (72 contributors) + Mohammed K. Fathy, Muhamad Arga Reksapati, Nicolas Grekas, ns-ylambert, + Paolo Ganci, Patrick Monnerat, rawsun007, Ray Satiro, Rémi NICOLE, + Renan Pimentel, renovate[bot], Rhino, Richard Payne, Ron Kuper, + Roshan Ramani, Rudolf Polzer, Samuel Henrique, Sebastian Raase, Skye Soss, + Stanislav Fort, Stefan Eissing, Stephen Psaradellis, szedenik-adam on github, + tawmoto on github, Thanniru Sai Teja, Tip ten Brink, Tyler Yankee, Un1q32, + Viktor Szakats, Wonyoung Jung, xhon-pelushi, Yechan Bae, yuwk + (84 contributors) References to bug reports and discussions on issues: @@ -426,6 +477,7 @@ References to bug reports and discussions on issues: [132] = https://curl.se/bug/?i=22950 [133] = https://curl.se/bug/?i=23086 [134] = https://curl.se/bug/?i=23085 + [135] = https://curl.se/bug/?i=23370 [136] = https://curl.se/bug/?i=23062 [137] = https://curl.se/bug/?i=22968 [138] = https://curl.se/bug/?i=22944 @@ -460,29 +512,43 @@ References to bug reports and discussions on issues: [167] = https://curl.se/bug/?i=23045 [168] = https://curl.se/bug/?i=23245 [169] = https://curl.se/bug/?i=23259 + [170] = https://curl.se/bug/?i=23334 [171] = https://curl.se/bug/?i=23014 [172] = https://curl.se/bug/?i=23280 [173] = https://curl.se/bug/?i=23037 [174] = https://curl.se/bug/?i=23038 [175] = https://curl.se/bug/?i=23035 [176] = https://curl.se/bug/?i=23261 + [177] = https://curl.se/bug/?i=23274 [178] = https://curl.se/bug/?i=23030 [179] = https://curl.se/bug/?i=23028 [180] = https://curl.se/bug/?i=23027 [181] = https://curl.se/bug/?i=23026 + [182] = https://curl.se/bug/?i=23327 [183] = https://github.com/git-for-windows/git/issues/6457 [184] = https://curl.se/bug/?i=23249 [185] = https://curl.se/bug/?i=23267 + [186] = https://curl.se/bug/?i=23326 [187] = https://curl.se/bug/?i=23265 + [188] = https://curl.se/bug/?i=23303 [189] = https://curl.se/bug/?i=23216 [190] = https://curl.se/bug/?i=23215 [191] = https://curl.se/bug/?i=23183 [192] = https://curl.se/bug/?i=23050 + [193] = https://curl.se/bug/?i=23313 + [194] = https://curl.se/bug/?i=23324 + [195] = https://curl.se/bug/?i=23318 + [196] = https://curl.se/bug/?i=23323 + [197] = https://curl.se/bug/?i=23322 [198] = https://curl.se/bug/?i=23210 [199] = https://curl.se/bug/?i=23220 + [200] = https://curl.se/bug/?i=23344 [201] = https://curl.se/bug/?i=23211 + [202] = https://curl.se/bug/?i=23317 [203] = https://curl.se/bug/?i=23208 + [204] = https://curl.se/bug/?i=23315 [205] = https://curl.se/bug/?i=23181 + [206] = https://curl.se/bug/?i=23314 [207] = https://curl.se/bug/?i=23192 [208] = https://curl.se/bug/?i=23189 [209] = https://curl.se/bug/?i=23169 @@ -490,32 +556,63 @@ References to bug reports and discussions on issues: [211] = https://curl.se/bug/?i=23165 [212] = https://curl.se/bug/?i=23162 [213] = https://curl.se/bug/?i=23156 + [214] = https://curl.se/bug/?i=23310 + [215] = https://curl.se/bug/?i=23296 [216] = https://curl.se/bug/?i=23146 [217] = https://curl.se/bug/?i=23199 [218] = https://curl.se/bug/?i=23197 + [219] = https://curl.se/bug/?i=23196 + [220] = https://curl.se/bug/?i=23136 + [221] = https://curl.se/bug/?i=23025 + [222] = https://curl.se/bug/?i=23238 + [223] = https://hackerone.com/reports/3971585 + [224] = https://curl.se/bug/?i=23254 + [225] = https://curl.se/bug/?i=23258 [226] = https://curl.se/bug/?i=23184 + [227] = https://curl.se/bug/?i=23299 [228] = https://curl.se/bug/?i=23167 [229] = https://curl.se/bug/?i=23164 + [230] = https://curl.se/bug/?i=23297 [231] = https://curl.se/bug/?i=23148 [232] = https://curl.se/bug/?i=23179 + [233] = https://curl.se/bug/?i=23300 + [234] = https://curl.se/bug/?i=23302 + [235] = https://curl.se/bug/?i=22614 + [236] = https://curl.se/bug/?i=23382 + [237] = https://curl.se/bug/?i=23298 [238] = https://curl.se/bug/?i=23171 + [239] = https://curl.se/bug/?i=23295 + [240] = https://curl.se/bug/?i=23024 + [241] = https://curl.se/bug/?i=23292 + [242] = https://curl.se/bug/?i=23212 [243] = https://curl.se/bug/?i=23163 + [244] = https://curl.se/bug/?i=23188 [245] = https://curl.se/bug/?i=23157 + [246] = https://curl.se/bug/?i=23262 [247] = https://curl.se/bug/?i=23153 + [248] = https://curl.se/bug/?i=23288 + [249] = https://curl.se/bug/?i=23377 [250] = https://curl.se/bug/?i=23149 [251] = https://curl.se/bug/?i=23150 [252] = https://curl.se/bug/?i=23147 + [253] = https://curl.se/bug/?i=23374 + [254] = https://curl.se/bug/?i=23339 [256] = https://curl.se/bug/?i=23142 [257] = https://curl.se/bug/?i=23140 + [258] = https://curl.se/bug/?i=23306 [259] = https://curl.se/bug/?i=23139 [260] = https://curl.se/bug/?i=23138 [261] = https://curl.se/bug/?i=23135 [262] = https://curl.se/bug/?i=23126 [263] = https://curl.se/bug/?i=23133 + [264] = https://curl.se/bug/?i=23363 + [266] = https://curl.se/bug/?i=23312 [267] = https://curl.se/bug/?i=23129 [271] = https://curl.se/bug/?i=23122 + [272] = https://curl.se/bug/?i=23306 [273] = https://curl.se/bug/?i=23120 [275] = https://curl.se/bug/?i=23084 + [276] = https://curl.se/bug/?i=23345 [279] = https://curl.se/bug/?i=23112 [282] = https://curl.se/bug/?i=23111 [289] = https://curl.se/bug/?i=23104 @@ -526,3 +623,6 @@ References to bug reports and discussions on issues: [297] = https://curl.se/bug/?i=23091 [298] = https://curl.se/bug/?i=23092 [299] = https://curl.se/bug/?i=23090 + [300] = https://curl.se/bug/?i=22719 + [301] = https://curl.se/bug/?i=23350 + [302] = https://curl.se/bug/?i=23368 diff --git a/docs/THANKS-filter b/docs/THANKS-filter index bcf6055b2cda..5dd1e6e27cb4 100644 --- a/docs/THANKS-filter +++ b/docs/THANKS-filter @@ -165,3 +165,5 @@ s/Val S\./Valerie Snyder/ s/Andrew Nesbit$/Andrew Nesbitt/ s/^https:\/\/.*// s/OSS-Fuzz// +s/Claude// +s/Copilot// From 1f3106fe25da2d221bc7b20ef8bcc34fad347a73 Mon Sep 17 00:00:00 2001 From: Rhino <195818135+1rhino2@users.noreply.github.com> Date: Thu, 15 Oct 2026 06:41:54 -0400 Subject: [PATCH 480/481] conncache: poll an idle connection for pending input on every reuse Since 2f1e94eaed the alive check in Curl_cpool_conn_seems_healthy() is skipped when it last ran less than a second ago, and that also skipped the pending input check that follows it. An idle HTTP/1.1 connection that had received unsolicited data from the server was then reused within that second and the stray bytes came back as the response to the next request, so every later response on that connection was shifted by one. 8.21.0 refused to reuse a connection with input pending on every reuse, and with 8.22.0 the tool reproduces it at a 900 ms gap between requests while 1100 ms gets a fresh connection. RFC 9112 section 9.2 says a client must not treat data received with no outstanding request as a response, so poll for pending input on every reuse of an idle non-multiplexed connection and keep the once a second limit only for the other checks, the protocol specific connection_is_dead callback and multiplexed connections. Test 1701 reproduces it with sws sending a second response after the first one completed and the tool starting the next request inside the window. Follow-up to 2f1e94eaed Ref: #22169 Bug: https://hackerone.com/reports/4098761 Closes #23412 (cherry picked from commit ef6b17cf12d4af4dfb2af4f9bc35861772be71c7) --- lib/conncache.c | 14 ++++++-- tests/data/Makefile.am | 1 + tests/data/test1701 | 75 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 87 insertions(+), 3 deletions(-) create mode 100644 tests/data/test1701 diff --git a/lib/conncache.c b/lib/conncache.c index 2f759bed2f01..157db0f2d83a 100644 --- a/lib/conncache.c +++ b/lib/conncache.c @@ -1044,11 +1044,20 @@ bool Curl_cpool_conn_seems_healthy(struct connectdata *conn, { struct Curl_easy *admin; bool healthy = TRUE; + bool poll_input; DEBUGASSERT(!data->conn); if(cpool_conn_can_be_closed(conn) && cpool_conn_too_old(data, conn, pnow)) return FALSE; - if((curlx_ptimediff_ms(pnow, &conn->created) - conn->lastchecked_ms) < 1000) + + /* An idle, non-multiplexed connection must not be reused when it has + * input pending (RFC 9112, section 9.2), so that is polled on every + * reuse. The once a second limit only applies to the other checks. */ + poll_input = cpool_conn_can_be_closed(conn) && + !Curl_conn_is_multiplex(conn, FIRSTSOCKET) && + !conn->scheme->run->connection_is_dead; + if(!poll_input && + (curlx_ptimediff_ms(pnow, &conn->created) - conn->lastchecked_ms) < 1000) return TRUE; admin = Curl_get_admin(data); @@ -1063,8 +1072,7 @@ bool Curl_cpool_conn_seems_healthy(struct connectdata *conn, Curl_attach_connection(admin, conn, FALSE); healthy = Curl_conn_is_alive(admin, conn, &input_pending); Curl_detach_connection(admin); - if(healthy && input_pending && cpool_conn_can_be_closed(conn) && - !Curl_conn_is_multiplex(conn, FIRSTSOCKET)) { + if(healthy && input_pending && poll_input) { /* Non-multiplexed connections without attached transfers should * not have input pending. The input might be a TLS Notify Close, * for all we know. */ diff --git a/tests/data/Makefile.am b/tests/data/Makefile.am index d066f2db583d..0f4ce61062c2 100644 --- a/tests/data/Makefile.am +++ b/tests/data/Makefile.am @@ -1716,6 +1716,7 @@ TESTCASES = \ test1693 \ \ test1700 \ + test1701 \ test1702 \ test1703 \ test1704 \ diff --git a/tests/data/test1701 b/tests/data/test1701 new file mode 100644 index 000000000000..e4e3d044302a --- /dev/null +++ b/tests/data/test1701 @@ -0,0 +1,75 @@ + + + + +HTTP +HTTP GET +DELAY + + +# Server-side + +# the first response is exactly 60 bytes so that with writedelay the server +# sends it in three 20 byte writes and the unsolicited second response only +# starts arriving after the first transfer is complete and the connection +# has been returned to the pool + +HTTP/1.1 200 OK +Funny-head: yesyes +Content-Length: 6 + +first +HTTP/1.1 200 OK +Content-Length: 6 + +stray + + +HTTP/1.1 200 OK +Content-Length: 7 + +second + + +writedelay: 150 + + + +# Client-side + + +http + + +HTTP/1.1 idle connection with unsolicited data is not reused + +# --rate 1/s starts the second request 1000 ms after the first one started, +# which is about 700 ms after the first response completed and the +# connection went back to the pool, inside the one second window where the +# pool used to skip the alive check. The stray response has fully arrived +# about 750 ms in. + +http://%HOSTIP:%HTTPPORT/%TESTNUMBER http://%HOSTIP:%HTTPPORT/%TESTNUMBER0002 --rate 1/s + + + +# Verify data after the test has been "shot" + + +GET /%TESTNUMBER HTTP/1.1 +Host: %HOSTIP:%HTTPPORT +User-Agent: curl/%VERSION +Accept: */* + +GET /%TESTNUMBER0002 HTTP/1.1 +Host: %HOSTIP:%HTTPPORT +User-Agent: curl/%VERSION +Accept: */* + + + +first +second + + + From fad47706f5c02e59fd45e291a0c72a325f42cb9b Mon Sep 17 00:00:00 2001 From: Simon Lemay Date: Mon, 26 Oct 2026 13:14:34 -0400 Subject: [PATCH 481/481] Update comment in vtls_config.h --- lib/vtls/vtls_config.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/vtls/vtls_config.h b/lib/vtls/vtls_config.h index 73a369a45749..c2f5c609cf97 100644 --- a/lib/vtls/vtls_config.h +++ b/lib/vtls/vtls_config.h @@ -53,8 +53,8 @@ struct ssl_filter_config { struct curl_blob *key_blob; char *curves; /* list of curves to use */ #if UNITY_CERTVERIFY - /* Unity: peer verification callback and its userdata. These live in the - primary config rather than in ssl_config_data so that they take part in + /* Unity: peer verification callback and its userdata, copied from + ssl_easy_config. They live in the filter config so that they take part in the connection reuse check, i.e. a connection verified by one callback is never reused by a transfer carrying a different one. */ curl_unity_certverify_callback unity_certverify;