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
This commit is contained in:
Daniel Stenberg
2025-12-23 13:54:12 +01:00
parent cb9db70dbe
commit 7e064d0756
7 changed files with 143 additions and 154 deletions

View File

@@ -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)

View File

@@ -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)

View File

@@ -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;
}

View File

@@ -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 *);

View File

@@ -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
</data>
<connect crlf="headers">
HTTP/1.1 200 Sure go ahead
Server: from the connect
Server: from%SP
%SPthe%TAB
%TAB%TABconnect
Silly-thing: yes yes
</connect>

View File

@@ -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");

View File

@@ -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);