From 7e064d0756c1b77a793a103f46add9bfdc22c680 Mon Sep 17 00:00:00 2001 From: Daniel Stenberg Date: Tue, 23 Dec 2025 13:54:12 +0100 Subject: [PATCH] cf-h1-proxy: support folded headers in CONNECT responses Update test 1941 to verify this Remove unused code from dynhds for handling folded headers, and the associated unit tests of those functions in test 2602 and 2603. Closes #20080 --- lib/cf-h1-proxy.c | 171 +++++++++++++++++++++++++++--------------- lib/dynhds.c | 79 +++++-------------- lib/http.c | 15 ++-- lib/http.h | 2 + tests/data/test1941 | 7 +- tests/unit/unit2602.c | 12 --- tests/unit/unit2603.c | 11 --- 7 files changed, 143 insertions(+), 154 deletions(-) diff --git a/lib/cf-h1-proxy.c b/lib/cf-h1-proxy.c index 83b8239d45..5c764bb59f 100644 --- a/lib/cf-h1-proxy.c +++ b/lib/cf-h1-proxy.c @@ -69,6 +69,8 @@ struct h1_tunnel_state { h1_tunnel_state tunnel_state; BIT(chunked_encoding); BIT(close_connection); + BIT(maybe_folded); + BIT(leading_unfold); }; static bool tunnel_is_established(struct h1_tunnel_state *ts) @@ -94,6 +96,8 @@ static CURLcode tunnel_reinit(struct Curl_cfilter *cf, ts->keepon = KEEPON_CONNECT; ts->cl = 0; ts->close_connection = FALSE; + ts->maybe_folded = FALSE; + ts->leading_unfold = FALSE; return CURLE_OK; } @@ -345,16 +349,82 @@ static CURLcode on_resp_header(struct Curl_cfilter *cf, return result; } +static CURLcode single_header(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct h1_tunnel_state *ts) +{ + CURLcode result = CURLE_OK; + char *linep = curlx_dyn_ptr(&ts->rcvbuf); + size_t line_len = curlx_dyn_len(&ts->rcvbuf); /* bytes in this line */ + struct SingleRequest *k = &data->req; + int writetype; + ts->headerlines++; + + /* output debug if that is requested */ + Curl_debug(data, CURLINFO_HEADER_IN, linep, line_len); + + /* send the header to the callback */ + writetype = CLIENTWRITE_HEADER | CLIENTWRITE_CONNECT | + (ts->headerlines == 1 ? CLIENTWRITE_STATUS : 0); + result = Curl_client_write(data, writetype, linep, line_len); + if(result) + return result; + + result = Curl_bump_headersize(data, line_len, TRUE); + if(result) + return result; + + /* Newlines are CRLF, so the CR is ignored as the line is not + really terminated until the LF comes. Treat a following CR + as end-of-headers as well.*/ + + if(ISNEWLINE(linep[0])) { + /* end of response-headers from the proxy */ + + 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 */ + ts->keepon = KEEPON_IGNORE; + + if(ts->cl) { + infof(data, "Ignore %" FMT_OFF_T " bytes of response-body", ts->cl); + } + else if(ts->chunked_encoding) { + infof(data, "Ignore chunked response-body"); + } + else { + /* without content-length or chunked encoding, we + cannot keep the connection alive since the close is + the end signal so we bail out at once instead */ + CURL_TRC_CF(data, cf, "CONNECT: no content-length or chunked"); + ts->keepon = KEEPON_DONE; + } + } + else { + ts->keepon = KEEPON_DONE; + } + + DEBUGASSERT(ts->keepon == KEEPON_IGNORE || + ts->keepon == KEEPON_DONE); + return result; + } + + result = on_resp_header(cf, data, ts, linep); + if(result) + return result; + + curlx_dyn_reset(&ts->rcvbuf); + return result; +} + static CURLcode recv_CONNECT_resp(struct Curl_cfilter *cf, struct Curl_easy *data, struct h1_tunnel_state *ts, bool *done) { CURLcode result = CURLE_OK; - struct SingleRequest *k = &data->req; - char *linep; - size_t line_len; - int error, writetype; + int error; #define SELECT_OK 0 #define SELECT_ERROR 1 @@ -428,6 +498,31 @@ static CURLcode recv_CONNECT_resp(struct Curl_cfilter *cf, continue; } + if(ts->maybe_folded) { + if(ISBLANK(byte)) { + Curl_http_to_fold(&ts->rcvbuf); + ts->leading_unfold = TRUE; + } + else { + result = single_header(cf, data, ts); + if(result) + return result; + /* now handle the new byte */ + } + ts->maybe_folded = FALSE; + } + + if(ts->leading_unfold) { + if(ISBLANK(byte)) + /* skip a bit brother */ + continue; + /* non-blank, insert a space then continue the unfolding */ + if(curlx_dyn_addn(&ts->rcvbuf, " ", 1)) { + failf(data, "CONNECT response too large"); + return CURLE_RECV_ERROR; + } + ts->leading_unfold = FALSE; + } if(curlx_dyn_addn(&ts->rcvbuf, &byte, 1)) { failf(data, "CONNECT response too large"); return CURLE_RECV_ERROR; @@ -436,67 +531,19 @@ static CURLcode recv_CONNECT_resp(struct Curl_cfilter *cf, /* if this is not the end of a header line then continue */ if(byte != 0x0a) continue; - - ts->headerlines++; - linep = curlx_dyn_ptr(&ts->rcvbuf); - line_len = curlx_dyn_len(&ts->rcvbuf); /* amount of bytes in this line */ - - /* output debug if that is requested */ - Curl_debug(data, CURLINFO_HEADER_IN, linep, line_len); - - /* send the header to the callback */ - writetype = CLIENTWRITE_HEADER | CLIENTWRITE_CONNECT | - (ts->headerlines == 1 ? CLIENTWRITE_STATUS : 0); - result = Curl_client_write(data, writetype, linep, line_len); - if(result) - return result; - - result = Curl_bump_headersize(data, line_len, TRUE); - if(result) - return result; - - /* Newlines are CRLF, so the CR is ignored as the line is not - really terminated until the LF comes. Treat a following CR - as end-of-headers as well.*/ - - if(('\r' == linep[0]) || - ('\n' == linep[0])) { - /* end of response-headers from the proxy */ - - 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 */ - ts->keepon = KEEPON_IGNORE; - - if(ts->cl) { - infof(data, "Ignore %" FMT_OFF_T " bytes of response-body", ts->cl); - } - else if(ts->chunked_encoding) { - infof(data, "Ignore chunked response-body"); - } - else { - /* without content-length or chunked encoding, we - cannot keep the connection alive since the close is - the end signal so we bail out at once instead */ - CURL_TRC_CF(data, cf, "CONNECT: no content-length or chunked"); - ts->keepon = KEEPON_DONE; - } + else { + char *linep = curlx_dyn_ptr(&ts->rcvbuf); + size_t hlen = curlx_dyn_len(&ts->rcvbuf); + if(hlen && ISNEWLINE(linep[0])) { + /* end of headers */ + result = single_header(cf, data, ts); + if(result) + return result; } - else { - ts->keepon = KEEPON_DONE; - } - - DEBUGASSERT(ts->keepon == KEEPON_IGNORE || - ts->keepon == KEEPON_DONE); - continue; + else + ts->maybe_folded = TRUE; } - result = on_resp_header(cf, data, ts, linep); - if(result) - return result; - - curlx_dyn_reset(&ts->rcvbuf); } /* while there is buffer left and loop is requested */ if(error) diff --git a/lib/dynhds.c b/lib/dynhds.c index d0a3eb6a74..3a4c80de69 100644 --- a/lib/dynhds.c +++ b/lib/dynhds.c @@ -56,29 +56,6 @@ entry_new(const char *name, size_t namelen, return e; } -static struct dynhds_entry *entry_append(struct dynhds_entry *e, - const char *value, size_t valuelen) -{ - struct dynhds_entry *e2; - size_t valuelen2 = e->valuelen + 1 + valuelen; - char *p; - - DEBUGASSERT(value); - e2 = curlx_calloc(1, sizeof(*e) + e->namelen + valuelen2 + 2); - if(!e2) - return NULL; - e2->name = p = ((char *)e2) + sizeof(*e2); - memcpy(p, e->name, e->namelen); - e2->namelen = e->namelen; - e2->value = p += e->namelen + 1; /* leave a \0 at the end of name */ - memcpy(p, e->value, e->valuelen); - p += e->valuelen; - p[0] = ' '; - memcpy(p + 1, value, valuelen); - e2->valuelen = valuelen2; - return e2; -} - static void entry_free(struct dynhds_entry *e) { curlx_free(e); @@ -222,48 +199,26 @@ CURLcode Curl_dynhds_h1_add_line(struct dynhds *dynhds, if(!line || !line_len) return CURLE_OK; - if((line[0] == ' ') || (line[0] == '\t')) { - struct dynhds_entry *e, *e2; - /* header continuation, yikes! */ - if(!dynhds->hds_len) - return CURLE_BAD_FUNCTION_ARGUMENT; - - while(line_len && ISBLANK(line[0])) { - ++line; - --line_len; - } - if(!line_len) - return CURLE_BAD_FUNCTION_ARGUMENT; - e = dynhds->hds[dynhds->hds_len - 1]; - e2 = entry_append(e, line, line_len); - if(!e2) - return CURLE_OUT_OF_MEMORY; - dynhds->hds[dynhds->hds_len - 1] = e2; - entry_free(e); - return CURLE_OK; + p = memchr(line, ':', line_len); + if(!p) + return CURLE_BAD_FUNCTION_ARGUMENT; + name = line; + namelen = p - line; + p++; /* move past the colon */ + for(i = namelen + 1; i < line_len; ++i, ++p) { + if(!ISBLANK(*p)) + break; } - else { - p = memchr(line, ':', line_len); - if(!p) - return CURLE_BAD_FUNCTION_ARGUMENT; - name = line; - namelen = p - line; - p++; /* move past the colon */ - for(i = namelen + 1; i < line_len; ++i, ++p) { - if(!ISBLANK(*p)) - break; - } - value = p; - valuelen = line_len - i; + value = p; + valuelen = line_len - i; - p = memchr(value, '\r', valuelen); - if(!p) - p = memchr(value, '\n', valuelen); - if(p) - valuelen = (size_t)(p - value); + p = memchr(value, '\r', valuelen); + if(!p) + p = memchr(value, '\n', valuelen); + if(p) + valuelen = (size_t)(p - value); - return Curl_dynhds_add(dynhds, name, namelen, value, valuelen); - } + return Curl_dynhds_add(dynhds, name, namelen, value, valuelen); } CURLcode Curl_dynhds_h1_cadd_line(struct dynhds *dynhds, const char *line) diff --git a/lib/http.c b/lib/http.c index bf38d35dde..3aabd0f7be 100644 --- a/lib/http.c +++ b/lib/http.c @@ -4288,18 +4288,23 @@ static CURLcode http_rw_hd(struct Curl_easy *data, return CURLE_OK; } -/* cut off the newline characters */ -static void unfold_header(struct Curl_easy *data) +/* remove trailing CRLF then all trailing whitespace */ +void Curl_http_to_fold(struct dynbuf *bf) { - size_t len = curlx_dyn_len(&data->state.headerb); - char *hd = curlx_dyn_ptr(&data->state.headerb); + size_t len = curlx_dyn_len(bf); + char *hd = curlx_dyn_ptr(bf); if(len && (hd[len - 1] == '\n')) len--; if(len && (hd[len - 1] == '\r')) len--; while(len && (ISBLANK(hd[len - 1]))) /* strip off trailing whitespace */ len--; - curlx_dyn_setlen(&data->state.headerb, len); + curlx_dyn_setlen(bf, len); +} + +static void unfold_header(struct Curl_easy *data) +{ + Curl_http_to_fold(&data->state.headerb); data->state.leading_unfold = TRUE; } diff --git a/lib/http.h b/lib/http.h index 7f944ea33b..89810b1757 100644 --- a/lib/http.h +++ b/lib/http.h @@ -104,6 +104,8 @@ CURLcode Curl_add_custom_headers(struct Curl_easy *data, bool is_connect, CURLcode Curl_dynhds_add_custom(struct Curl_easy *data, bool is_connect, struct dynhds *hds); +void Curl_http_to_fold(struct dynbuf *bf); + void Curl_http_method(struct Curl_easy *data, const char **method, Curl_HttpReq *); diff --git a/tests/data/test1941 b/tests/data/test1941 index 0542b7b943..4532403bc9 100644 --- a/tests/data/test1941 +++ b/tests/data/test1941 @@ -13,7 +13,8 @@ CONNECT HTTP/1.1 200 OK Date: Thu, 09 Nov 2010 14:49:00 GMT Server: test with trailing space%repeat[5 x ]% -Content-Type: text/html +Content-Type: +%SPtext/html Content-Length: 0 Set-Cookie: onecookie=data; Set-Cookie: secondcookie=2data; @@ -23,7 +24,9 @@ Location: /%TESTNUMBER0002 HTTP/1.1 200 Sure go ahead -Server: from the connect +Server: from%SP +%SPthe%TAB +%TAB%TABconnect Silly-thing: yes yes diff --git a/tests/unit/unit2602.c b/tests/unit/unit2602.c index 684fa3db83..c33e8d63d1 100644 --- a/tests/unit/unit2602.c +++ b/tests/unit/unit2602.c @@ -111,18 +111,6 @@ static CURLcode test_unit2602(const char *arg) } Curl_dynhds_free(&hds); - Curl_dynhds_init(&hds, 128, 4 * 1024); - /* continuation without previous header fails */ - res = Curl_dynhds_h1_cadd_line(&hds, " indented value"); - fail_unless(res, "add should have failed"); - - /* continuation with previous header must succeed */ - fail_if(Curl_dynhds_h1_cadd_line(&hds, "ti1: val1"), "add"); - fail_if(Curl_dynhds_h1_cadd_line(&hds, " val2"), "add indent"); - fail_if(Curl_dynhds_h1_cadd_line(&hds, "ti2: val1"), "add"); - fail_if(Curl_dynhds_h1_cadd_line(&hds, "\tval2"), "add indent"); - fail_if(Curl_dynhds_h1_cadd_line(&hds, "ti3: val1"), "add"); - fail_if(Curl_dynhds_h1_cadd_line(&hds, " val2"), "add indent"); curlx_dyn_init(&dbuf, 32 * 1024); fail_if(Curl_dynhds_h1_dprint(&hds, &dbuf), "h1 print failed"); diff --git a/tests/unit/unit2603.c b/tests/unit/unit2603.c index 1de774d26f..b3ade82a62 100644 --- a/tests/unit/unit2603.c +++ b/tests/unit/unit2603.c @@ -159,16 +159,6 @@ static CURLcode test_unit2603(const char *arg) T4_INPUT, NULL, NULL, "CONNECT", NULL, "ftp.curl.se:123", NULL, 3, 2 }; - static const char *T5_INPUT[] = { - "OPTIONS * HTTP/1.1\r\nContent-Length: 0\r\nBlabla: xxx.yyy\r", - "\n\tzzzzzz\r\n\r\n", - "123", - NULL, - }; - static const struct tcase TEST5a = { - T5_INPUT, NULL, NULL, "OPTIONS", NULL, NULL, "*", 2, 3 - }; - static const char *T6_INPUT[] = { "PUT /path HTTP/1.1\nHost: test.curl.se\n\n123", NULL, @@ -194,7 +184,6 @@ static CURLcode test_unit2603(const char *arg) parse_success(&TEST2); parse_success(&TEST3a); parse_success(&TEST4a); - parse_success(&TEST5a); parse_success(&TEST6a); parse_success(&TEST7a); parse_success(&TEST7b);