From 40f7cd2bdd14e09b9bb1b4ddefc8b5e5ce71865a Mon Sep 17 00:00:00 2001 From: Stefan Eissing Date: Tue, 21 Oct 2025 13:51:10 +0200 Subject: [PATCH] mime: fix unpausing of readers MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit When unpausing a transfer, check if the reader pause state differs in addition to the "keepon" flags. Reported-by: 包布丁 Fixes #18848 Closes #19178 --- lib/easy.c | 3 +- lib/sendf.c | 1 + tests/http/test_07_upload.py | 19 ++++++++++ tests/http/testenv/httpd.py | 6 ++-- .../http/testenv/mod_curltest/mod_curltest.c | 15 ++++++++ tests/libtest/cli_hx_upload.c | 36 +++++++++++++------ 6 files changed, 67 insertions(+), 13 deletions(-) diff --git a/lib/easy.c b/lib/easy.c index 793a18f33e..4236a01d87 100644 --- a/lib/easy.c +++ b/lib/easy.c @@ -1165,7 +1165,8 @@ CURLcode curl_easy_pause(CURL *d, int action) send_paused = Curl_xfer_send_is_paused(data); send_paused_new = (action & CURLPAUSE_SEND); - if(send_paused != send_paused_new) { + if((send_paused != send_paused_new) || + (send_paused_new != Curl_creader_is_paused(data))) { changed = TRUE; result = Curl_1st_err(result, Curl_xfer_pause_send(data, send_paused_new)); } diff --git a/lib/sendf.c b/lib/sendf.c index c6d8412762..7307f872df 100644 --- a/lib/sendf.c +++ b/lib/sendf.c @@ -1442,6 +1442,7 @@ CURLcode Curl_creader_unpause(struct Curl_easy *data) while(reader) { result = reader->crt->cntrl(data, reader, CURL_CRCNTRL_UNPAUSE); + CURL_TRC_READ(data, "unpausing %s -> %d", reader->crt->name, result); if(result) break; reader = reader->next; diff --git a/tests/http/test_07_upload.py b/tests/http/test_07_upload.py index d17d3a44ad..c2377d41e4 100644 --- a/tests/http/test_07_upload.py +++ b/tests/http/test_07_upload.py @@ -671,6 +671,25 @@ class TestUpload: ]) r.check_stats(count=1, http_status=200, exitcode=0) + @pytest.mark.parametrize("proto", ['http/1.1']) + def test_07_63_upload_exp100_paused(self, env: Env, httpd, nghttpx, proto): + read_delay = 1 + url = f'https://{env.authority_for(env.domain1, proto)}/curltest/echo?id=[0-0]'\ + f'&read_delay={read_delay}s' + upload_size = 128 * 1024 + client = LocalClient(name='cli_hx_upload', env=env) + if not client.exists(): + pytest.skip(f'example client not built: {client.name}') + r = client.run(args=[ + '-n', '1', + '-S', f'{upload_size}', + '-P', '1', + '-M', 'MIME', + '-r', f'{env.domain1}:{env.port_for(proto)}:127.0.0.1', + '-V', proto, url + ]) + r.check_exit_code(0) + # nghttpx is the only server we have that supports TLS early data and # has a limit of 16k it announces @pytest.mark.skipif(condition=not Env.have_nghttpx(), reason="no nghttpx") diff --git a/tests/http/testenv/httpd.py b/tests/http/testenv/httpd.py index d9f535dd22..740237abc4 100644 --- a/tests/http/testenv/httpd.py +++ b/tests/http/testenv/httpd.py @@ -575,11 +575,13 @@ class Httpd: return local_dir = os.path.dirname(inspect.getfile(Httpd)) out_dir = os.path.join(self.env.gen_dir, 'mod_curltest') + in_source = os.path.join(local_dir, 'mod_curltest/mod_curltest.c') out_source = os.path.join(out_dir, 'mod_curltest.c') if not os.path.exists(out_dir): os.mkdir(out_dir) - if not os.path.exists(out_source): - shutil.copy(os.path.join(local_dir, 'mod_curltest/mod_curltest.c'), out_source) + if not os.path.exists(out_source) or \ + os.stat(in_source).st_mtime > os.stat(out_source).st_mtime: + shutil.copy(in_source, out_source) p = subprocess.run([ self.env.apxs, '-c', out_source ], capture_output=True, cwd=out_dir) diff --git a/tests/http/testenv/mod_curltest/mod_curltest.c b/tests/http/testenv/mod_curltest/mod_curltest.c index e354e5a469..17d0688ace 100644 --- a/tests/http/testenv/mod_curltest/mod_curltest.c +++ b/tests/http/testenv/mod_curltest/mod_curltest.c @@ -188,6 +188,7 @@ static int curltest_echo_handler(request_rec *r) char buffer[8192]; const char *ct; apr_off_t die_after_len = -1, total_read_len = 0; + apr_time_t read_delay = 0; int just_die = 0, die_after_100 = 0; long l; @@ -221,6 +222,12 @@ static int curltest_echo_handler(request_rec *r) die_after_100 = 1; continue; } + else if(!strcmp("read_delay", arg)) { + rv = duration_parse(&read_delay, val, "s"); + if(APR_SUCCESS == rv) { + continue; + } + } } } } @@ -258,6 +265,12 @@ static int curltest_echo_handler(request_rec *r) apr_table_setn(r->headers_out, "Request-TE", apr_table_get(r->headers_in, "TE")); + if(read_delay) { + ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, + "put_handler: read_delay"); + apr_sleep(read_delay); + } + bb = apr_brigade_create(r->pool, c->bucket_alloc); /* copy any request body into the response */ rv = ap_setup_client_block(r, REQUEST_CHUNKED_DECHUNK); @@ -637,6 +650,8 @@ static int curltest_put_handler(request_rec *r) ap_set_content_type(r, ct ? ct : "text/plain"); if(read_delay) { + ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, + "put_handler: read_delay"); apr_sleep(read_delay); } bb = apr_brigade_create(r->pool, c->bucket_alloc); diff --git a/tests/libtest/cli_hx_upload.c b/tests/libtest/cli_hx_upload.c index a0a6b95db8..5d9c2f26f8 100644 --- a/tests/libtest/cli_hx_upload.c +++ b/tests/libtest/cli_hx_upload.c @@ -33,6 +33,7 @@ struct transfer_u { CURL *easy; const char *method; char filename[128]; + curl_mime *mime; FILE *out; curl_off_t send_total; curl_off_t recv_size; @@ -158,18 +159,28 @@ static int setup_hx_upload(CURL *hnd, const char *url, struct transfer_u *t, if(use_earlydata) curl_easy_setopt(hnd, CURLOPT_SSL_OPTIONS, CURLSSLOPT_EARLYDATA); - if(!t->method || !strcmp("PUT", t->method)) - curl_easy_setopt(hnd, CURLOPT_UPLOAD, 1L); - else if(!strcmp("POST", t->method)) - curl_easy_setopt(hnd, CURLOPT_POST, 1L); + if(!strcmp("MIME", t->method)) { + curl_mimepart *part; + t->mime = curl_mime_init(hnd); + part = curl_mime_addpart(t->mime); + curl_mime_name(part, "file"); + curl_mime_data_cb(part, -1, my_read_cb, NULL, NULL, t); + curl_easy_setopt(hnd, CURLOPT_MIMEPOST, t->mime); + } else { - curl_mfprintf(stderr, "unsupported method '%s'\n", t->method); - return 1; + if(!t->method || !strcmp("PUT", t->method)) + curl_easy_setopt(hnd, CURLOPT_UPLOAD, 1L); + else if(!strcmp("POST", t->method)) + curl_easy_setopt(hnd, CURLOPT_POST, 1L); + else { + curl_mfprintf(stderr, "unsupported method '%s'\n", t->method); + return 1; + } + curl_easy_setopt(hnd, CURLOPT_READFUNCTION, my_read_cb); + curl_easy_setopt(hnd, CURLOPT_READDATA, t); + if(announce_length) + curl_easy_setopt(hnd, CURLOPT_INFILESIZE_LARGE, t->send_total); } - curl_easy_setopt(hnd, CURLOPT_READFUNCTION, my_read_cb); - curl_easy_setopt(hnd, CURLOPT_READDATA, t); - if(announce_length) - curl_easy_setopt(hnd, CURLOPT_INFILESIZE_LARGE, t->send_total); curl_easy_setopt(hnd, CURLOPT_NOPROGRESS, 0L); curl_easy_setopt(hnd, CURLOPT_XFERINFOFUNCTION, my_progress_u_cb); @@ -488,6 +499,7 @@ static CURLcode test_cli_hx_upload(const char *URL) } while(active_transfers); /* as long as we have transfers going */ + curl_mfprintf(stderr, "all transfers done, cleanup multi\n"); curl_multi_cleanup(multi_handle); } @@ -501,9 +513,13 @@ static CURLcode test_cli_hx_upload(const char *URL) curl_easy_cleanup(t->easy); t->easy = NULL; } + if(t->mime) { + curl_mime_free(t->mime); + } } free(transfer_u); curl_share_cleanup(share); + curl_slist_free_all(host); return CURLE_OK; } -- 2.47.3