← Commit list

7e064d0756

Daniel Stenberg
Daniel Stenberg
2025-12-23
SUCCESS Duration: 197s

Subject

cf-h1-proxy: support folded headers in CONNECT responses

Binary

No binary (build failed)

Architecture & Compiler Flags

Architecture: x86_64-linux-gnu Compiler: gcc (Ubuntu 11.4.0-1ubuntu1~22.04.3) 11.4.0

—

Build Log

(log yok)

Commit Diff GitHub →

lib/cf-h1-proxy.c+109 -62
@@ -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 {
- 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;
}
-
- 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)
lib/dynhds.c+17 -62
@@ -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)
lib/http.c+10 -5
@@ -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;
}
lib/http.h+2 -0
@@ -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 *);
tests/data/test1941+5 -2
@@ -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>
tests/unit/unit2602.c+0 -12
@@ -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");
tests/unit/unit2603.c+0 -11
@@ -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);