From c76df46a195dff259f2a2716f9f8fb5b7eb89602 Mon Sep 17 00:00:00 2001 From: Stefan Eissing Date: Mon, 24 Jul 2023 15:38:04 +0200 Subject: [PATCH] http: VLH, very large header test and fixes - adding tests using very large passwords in auth - fixes general http sending to treat h3 like h2, and not like http1.1 - eliminate H2_HEADER max definitions and use the commmon DYN_HTTP_REQUEST everywhere, different limits do not help - fix http2 handling of requests denied by nghttp2 on send to immediately report the refused stream Closes #11509 --- lib/dynbuf.h | 2 -- lib/http.c | 14 +++++------ lib/http2.c | 14 +++++++---- lib/vquic/curl_ngtcp2.c | 2 ++ tests/http/test_14_auth.py | 49 ++++++++++++++++++++++++++++++++++++++ 5 files changed, 68 insertions(+), 13 deletions(-) diff --git a/lib/dynbuf.h b/lib/dynbuf.h index 57ad62b22b..6291eabd37 100644 --- a/lib/dynbuf.h +++ b/lib/dynbuf.h @@ -81,8 +81,6 @@ int Curl_dyn_vprintf(struct dynbuf *dyn, const char *format, va_list ap_save); #define DYN_PAUSE_BUFFER (64 * 1024 * 1024) #define DYN_HAXPROXY 2048 #define DYN_HTTP_REQUEST (1024*1024) -#define DYN_H2_HEADERS (128*1024) -#define DYN_H2_TRAILERS (128*1024) #define DYN_APRINTF 8000000 #define DYN_RTSP_REQ_HEADER (64*1024) #define DYN_TRAILERS (64*1024) diff --git a/lib/http.c b/lib/http.c index e04028b3fe..e611d27895 100644 --- a/lib/http.c +++ b/lib/http.c @@ -1308,7 +1308,7 @@ CURLcode Curl_buffer_send(struct dynbuf *in, || IS_HTTPS_PROXY(conn->http_proxy.proxytype) #endif ) - && conn->httpversion != 20) { + && conn->httpversion < 20) { /* Make sure this doesn't send more body bytes than what the max send speed says. The request bytes do not count to the max speed. */ @@ -4571,8 +4571,8 @@ CURLcode Curl_http_req_make(struct httpreq **preq, if(!req->path) goto out; } - Curl_dynhds_init(&req->headers, 0, DYN_H2_HEADERS); - Curl_dynhds_init(&req->trailers, 0, DYN_H2_TRAILERS); + Curl_dynhds_init(&req->headers, 0, DYN_HTTP_REQUEST); + Curl_dynhds_init(&req->trailers, 0, DYN_HTTP_REQUEST); result = CURLE_OK; out: @@ -4729,8 +4729,8 @@ CURLcode Curl_http_req_make2(struct httpreq **preq, if(result) goto out; - Curl_dynhds_init(&req->headers, 0, DYN_H2_HEADERS); - Curl_dynhds_init(&req->trailers, 0, DYN_H2_TRAILERS); + Curl_dynhds_init(&req->headers, 0, DYN_HTTP_REQUEST); + Curl_dynhds_init(&req->trailers, 0, DYN_HTTP_REQUEST); result = CURLE_OK; out: @@ -4860,8 +4860,8 @@ CURLcode Curl_http_resp_make(struct http_resp **presp, if(!resp->description) goto out; } - Curl_dynhds_init(&resp->headers, 0, DYN_H2_HEADERS); - Curl_dynhds_init(&resp->trailers, 0, DYN_H2_TRAILERS); + Curl_dynhds_init(&resp->headers, 0, DYN_HTTP_REQUEST); + Curl_dynhds_init(&resp->trailers, 0, DYN_HTTP_REQUEST); result = CURLE_OK; out: diff --git a/lib/http2.c b/lib/http2.c index 64a066b84f..6c09ec1a1f 100644 --- a/lib/http2.c +++ b/lib/http2.c @@ -252,7 +252,7 @@ static CURLcode http2_data_setup(struct Curl_cfilter *cf, H2_STREAM_SEND_CHUNKS, BUFQ_OPT_NONE); Curl_bufq_initp(&stream->recvbuf, &ctx->stream_bufcp, H2_STREAM_RECV_CHUNKS, BUFQ_OPT_SOFT_LIMIT); - Curl_dynhds_init(&stream->resp_trailers, 0, DYN_H2_TRAILERS); + Curl_dynhds_init(&stream->resp_trailers, 0, DYN_HTTP_REQUEST); stream->resp_hds_len = 0; stream->bodystarted = FALSE; stream->status_code = -1; @@ -2122,7 +2122,13 @@ static ssize_t cf_h2_send(struct Curl_cfilter *cf, struct Curl_easy *data, /* Call the nghttp2 send loop and flush to write ALL buffered data, * headers and/or request body completely out to the network */ result = h2_progress_egress(cf, data); - if(result == CURLE_AGAIN) { + /* 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) { + nwritten = http2_handle_stream_close(cf, data, stream, err); + goto out; + } + else if(result == CURLE_AGAIN) { blocked = 1; } else if(result) { @@ -2130,14 +2136,14 @@ static ssize_t cf_h2_send(struct Curl_cfilter *cf, struct Curl_easy *data, nwritten = -1; goto out; } - else if(!Curl_bufq_is_empty(&stream->sendbuf)) { + else if(stream && !Curl_bufq_is_empty(&stream->sendbuf)) { /* although we wrote everything that nghttp2 wants to send now, * there is data left in our stream send buffer unwritten. This may * be due to the stream's HTTP/2 flow window being exhausted. */ blocked = 1; } - if(blocked) { + if(stream && blocked) { /* Unable to send all data, due to connection blocked or H2 window * exhaustion. Data is left in our stream buffer, or nghttp2's internal * frame buffer or our network out buffer. */ diff --git a/lib/vquic/curl_ngtcp2.c b/lib/vquic/curl_ngtcp2.c index 1d68fc0f1a..a430aa119a 100644 --- a/lib/vquic/curl_ngtcp2.c +++ b/lib/vquic/curl_ngtcp2.c @@ -1835,6 +1835,8 @@ out: *err = result; sent = -1; } + DEBUGF(LOG_CF(data, cf, "[h3sid=%" PRId64 "] cf_send(len=%zu) -> %zd, %d", + stream? stream->id : -1, len, sent, *err)); CF_DATA_RESTORE(cf, save); return sent; } diff --git a/tests/http/test_14_auth.py b/tests/http/test_14_auth.py index 23acb35989..d962cedb3c 100644 --- a/tests/http/test_14_auth.py +++ b/tests/http/test_14_auth.py @@ -42,6 +42,7 @@ class TestAuth: def _class_scope(self, env, httpd, nghttpx): if env.have_h3(): nghttpx.start_if_needed() + env.make_data_file(indir=env.gen_dir, fname="data-10m", fsize=10*1024*1024) httpd.clear_extra_configs() httpd.reload() @@ -79,3 +80,51 @@ class TestAuth: '--digest', '--user', 'test:test' ]) r.check_response(http_status=200) + + # PUT data, digest auth large pw + @pytest.mark.parametrize("proto", ['h2', 'h3']) + def test_14_04_digest_large_pw(self, env: Env, httpd, nghttpx, repeat, proto): + if proto == 'h3' and not env.have_h3(): + pytest.skip("h3 not supported") + data='0123456789' + password = 'x' * 65535 + curl = CurlClient(env=env) + url = f'https://{env.authority_for(env.domain1, proto)}/restricted/digest/data.json' + r = curl.http_upload(urls=[url], data=data, alpn_proto=proto, extra_args=[ + '--digest', '--user', f'test:{password}' + ]) + # digest does not submit the password, but a hash of it, so all + # works and, since the pw is not correct, we get a 401 + r.check_response(http_status=401) + + # PUT data, basic auth large pw + @pytest.mark.parametrize("proto", ['h2', 'h3']) + def test_14_05_basic_large_pw(self, env: Env, httpd, nghttpx, repeat, proto): + if proto == 'h3' and not env.have_h3(): + pytest.skip("h3 not supported") + # just large enought that nghttp2 will submit + password = 'x' * (47 * 1024) + fdata = os.path.join(env.gen_dir, 'data-10m') + curl = CurlClient(env=env) + url = f'https://{env.authority_for(env.domain1, proto)}/restricted/digest/data.json' + r = curl.http_upload(urls=[url], data=f'@{fdata}', alpn_proto=proto, extra_args=[ + '--basic', '--user', f'test:{password}' + ]) + # but apache denies on length limit + r.check_response(http_status=431) + + # PUT data, basic auth with very large pw + @pytest.mark.parametrize("proto", ['h2', 'h3']) + def test_14_06_basic_very_large_pw(self, env: Env, httpd, nghttpx, repeat, proto): + if proto == 'h3' and not env.have_h3(): + pytest.skip("h3 not supported") + data='0123456789' + password = 'x' * (64 * 1024) + fdata = os.path.join(env.gen_dir, 'data-10m') + curl = CurlClient(env=env) + url = f'https://{env.authority_for(env.domain1, proto)}/restricted/digest/data.json' + r = curl.http_upload(urls=[url], data=f'@{fdata}', alpn_proto=proto, extra_args=[ + '--basic', '--user', f'test:{password}' + ]) + # request was never sent + r.check_response(exitcode=55, http_status=0) -- 2.47.3