From: Daniel Stenberg Date: Fri, 12 Dec 2025 15:36:08 +0000 (+0100) Subject: http: unfold response headers earlier X-Git-Tag: rc-8_18_0-2~13 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=67ae101666f1023263c475b88b266bc9b5e658c0;p=thirdparty%2Fcurl.git http: unfold response headers earlier Make the low-level HTTP header "builder" unfold headers so that everything else can keep pretending folding does not exist. This code no longer tries to reduce repeated leading whitespace (in the continued folded header) to a single one. To avoid having to have a special state for that. Adjusted two test cases accordingly Closes #19949 --- diff --git a/lib/headers.c b/lib/headers.c index 46de5d107b..0cafcaa270 100644 --- a/lib/headers.c +++ b/lib/headers.c @@ -215,55 +215,6 @@ static CURLcode namevalue(char *header, size_t hlen, unsigned int type, return CURLE_OK; } -static CURLcode unfold_value(struct Curl_easy *data, const char *value, - size_t vlen) /* length of the incoming header */ -{ - struct Curl_header_store *hs; - struct Curl_header_store *newhs; - size_t olen; /* length of the old value */ - size_t oalloc; /* length of the old name + value + separator */ - size_t offset; - DEBUGASSERT(data->state.prevhead); - hs = data->state.prevhead; - olen = strlen(hs->value); - offset = hs->value - hs->buffer; - oalloc = olen + offset + 1; - - /* skip all trailing space letters */ - while(vlen && ISBLANK(value[vlen - 1])) - vlen--; - - /* save only one leading space */ - while((vlen > 1) && ISBLANK(value[0]) && ISBLANK(value[1])) { - vlen--; - value++; - } - - /* since this header block might move in the realloc below, it needs to - first be unlinked from the list and then re-added again after the - realloc */ - Curl_node_remove(&hs->node); - - /* new size = struct + new value length + old name+value length */ - newhs = Curl_saferealloc(hs, sizeof(*hs) + vlen + oalloc + 1); - if(!newhs) - return CURLE_OUT_OF_MEMORY; - /* ->name and ->value point into ->buffer (to keep the header allocation - in a single memory block), which now potentially have moved. Adjust - them. */ - newhs->name = newhs->buffer; - newhs->value = &newhs->buffer[offset]; - - /* put the data at the end of the previous data, not the newline */ - memcpy(&newhs->value[olen], value, vlen); - newhs->value[olen + vlen] = 0; /* null-terminate at newline */ - - /* insert this node into the list of headers */ - Curl_llist_append(&data->state.httphdrs, newhs, &newhs->node); - data->state.prevhead = newhs; - return CURLE_OK; -} - /* * Curl_headers_push() gets passed a full HTTP header to store. It gets called * immediately before the header callback. The header is CRLF, CR or LF @@ -292,20 +243,14 @@ CURLcode Curl_headers_push(struct Curl_easy *data, const char *header, /* neither CR nor LF as terminator is not a valid header */ return CURLE_WEIRD_SERVER_REPLY; - if((header[0] == ' ') || (header[0] == '\t')) { - if(data->state.prevhead) - /* line folding, append value to the previous header's value */ - return unfold_value(data, header, hlen); - else { - /* cannot unfold without a previous header. Instead of erroring, just - pass the leading blanks. */ - while(hlen && ISBLANK(*header)) { - header++; - hlen--; - } - if(!hlen) - return CURLE_WEIRD_SERVER_REPLY; + if(ISBLANK(header[0])) { + /* pass leading blanks */ + while(hlen && ISBLANK(*header)) { + header++; + hlen--; } + if(!hlen) + return CURLE_WEIRD_SERVER_REPLY; } if(Curl_llist_count(&data->state.httphdrs) >= MAX_HTTP_RESP_HEADER_COUNT) { failf(data, "Too many response headers, %d is max", diff --git a/lib/http.c b/lib/http.c index d5aca0e800..c80deb319e 100644 --- a/lib/http.c +++ b/lib/http.c @@ -111,6 +111,8 @@ static CURLcode http_statusline(struct Curl_easy *data, struct connectdata *conn); static CURLcode http_target(struct Curl_easy *data, struct dynbuf *req); static CURLcode http_useragent(struct Curl_easy *data); +static CURLcode http_write_header(struct Curl_easy *data, + const char *hd, size_t hdlen); /* * HTTP handler interface. @@ -1575,6 +1577,10 @@ CURLcode Curl_http_done(struct Curl_easy *data, data->state.authhost.multipass = FALSE; data->state.authproxy.multipass = FALSE; + if(curlx_dyn_len(&data->state.headerb)) { + (void)http_write_header(data, curlx_dyn_ptr(&data->state.headerb), + curlx_dyn_len(&data->state.headerb)); + } curlx_dyn_reset(&data->state.headerb); if(status) @@ -2947,6 +2953,7 @@ CURLcode Curl_http(struct Curl_easy *data, bool *done) /* make sure the header buffer is reset - if there are leftovers from a previous transfer */ curlx_dyn_reset(&data->state.headerb); + data->state.maybe_folded = FALSE; if(!data->conn->bits.reuse) { result = http_check_new_conn(data); @@ -4283,6 +4290,18 @@ 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) +{ + size_t len = curlx_dyn_len(&data->state.headerb); + char *hd = curlx_dyn_ptr(&data->state.headerb); + if(len && (hd[len -1] == '\n')) + len--; + if(len && (hd[len -1] == '\r')) + len--; + curlx_dyn_setlen(&data->state.headerb, len); +} + /* * Read any HTTP header lines from the server and pass them to the client app. */ @@ -4296,10 +4315,32 @@ static CURLcode http_parse_headers(struct Curl_easy *data, char *end_ptr; bool leftover_body = FALSE; + /* we have bytes for the next header, make sure it is not a folded header + before passing it on */ + if(data->state.maybe_folded && blen) { + if(ISBLANK(buf[0])) { + /* folded, remove the trailing newlines and append the next header */ + unfold_header(data); + } + else { + /* the header data we hold is a complete header, pass it on */ + size_t ignore_this; + result = http_rw_hd(data, curlx_dyn_ptr(&data->state.headerb), + curlx_dyn_len(&data->state.headerb), + NULL, 0, &ignore_this); + curlx_dyn_reset(&data->state.headerb); + if(result) + return result; + } + data->state.maybe_folded = FALSE; + } + /* header line within buffer loop */ *pconsumed = 0; while(blen && k->header) { size_t consumed; + size_t hlen; + char *hd; end_ptr = memchr(buf, '\n', blen); if(!end_ptr) { @@ -4350,11 +4391,12 @@ static CURLcode http_parse_headers(struct Curl_easy *data, * We now have a FULL header line in 'headerb'. *****/ + hlen = curlx_dyn_len(&data->state.headerb); + hd = curlx_dyn_ptr(&data->state.headerb); + if(!k->headerline) { - /* the first read header */ - statusline st = checkprotoprefix(data, conn, - curlx_dyn_ptr(&data->state.headerb), - curlx_dyn_len(&data->state.headerb)); + /* the first read "header", the status line */ + statusline st = checkprotoprefix(data, conn, hd, hlen); if(st == STATUS_BAD) { streamclose(conn, "bad HTTP: No end-of-message indicator"); /* this is not the beginning of a protocol first header line. @@ -4372,10 +4414,25 @@ static CURLcode http_parse_headers(struct Curl_easy *data, goto out; } } + else { + if(hlen && !ISNEWLINE(hd[0])) { + /* this is NOT the header separator */ + + /* if we have bytes for the next header, check for folding */ + if(blen && ISBLANK(buf[0])) { + /* remove the trailing CRLF and append the next header */ + unfold_header(data); + continue; + } + else if(!blen) { + /* this might be a folded header so deal with it in next invoke */ + data->state.maybe_folded = TRUE; + break; + } + } + } - result = http_rw_hd(data, curlx_dyn_ptr(&data->state.headerb), - curlx_dyn_len(&data->state.headerb), - buf, blen, &consumed); + result = http_rw_hd(data, hd, hlen, buf, blen, &consumed); /* We are done with this line. We reset because response * processing might switch to HTTP/2 and that might call us * directly again. */ diff --git a/lib/urldata.h b/lib/urldata.h index 7d5166e8e0..20a0a1cd5d 100644 --- a/lib/urldata.h +++ b/lib/urldata.h @@ -1133,6 +1133,7 @@ struct UrlState { BIT(http_hd_te); /* Added HTTP header TE: */ BIT(http_hd_upgrade); /* Added HTTP header Upgrade: */ BIT(http_hd_h2_settings); /* Added HTTP header H2Settings: */ + BIT(maybe_folded); #endif }; diff --git a/tests/data/test1274 b/tests/data/test1274 index 0cb75e9b08..99d23ad8db 100644 --- a/tests/data/test1274 +++ b/tests/data/test1274 @@ -51,14 +51,11 @@ Accept: */* HTTP/1.1 200 OK Date: Tue, 09 Nov 2010 14:49:00 GMT -Server: test-server/ - fake - folded +Server: test-server/ fake folded Last-Modified: Tue, 13 Jun 2000 12:10:00 GMT ETag: "21025-dc7-39462498" Content-Length: 6 -Connection:%repeat[46 x ]% - close +Connection:%repeat[46 x ]% close diff --git a/tests/data/test1940 b/tests/data/test1940 index 597adf851b..8f2f73437d 100644 --- a/tests/data/test1940 +++ b/tests/data/test1940 @@ -59,7 +59,7 @@ http://%HOSTIP:%HTTPPORT/%TESTNUMBER - Set-Cookie == onecookie=data; (0/3) - Set-Cookie == secondcookie=2data; (1/3) - Set-Cookie == cookie3=data3; (2/3) - Fold == is folding a line + Fold == is folding a line Blank ==%SP Blank2 ==%SP