Subject: Re: [PATCH 0/3] test-suite fixes for upcoming curl 8.18.0

From: Jeff King <[email-masked]>
Date: Fri, 19 Dec 2025 03:04:09 -0500

On Thu, Dec 18, 2025 at 05:49:27PM +0100, Daniel Stenberg wrote:

> On Thu, 18 Dec 2025, Daniel Stenberg wrote:
> 
> > >  [3/3]: t5563: relax whitespace assumptions for unfolded headers
> 
> > I did not fully consider the impact this might have on users such as
> > you. Allow me to rework that a little bit further and get the former
> > white-space behavior back. Thanks!
> 
> I just merged a fix [1] into curl that should restore the unfolding behavior
> to match previous releases. It would be awesome if you could verify.
> 
> [1] = https://github.com/curl/curl/commit/9941e7c95bf26f00fd87888a

Thanks, I took a look at it. Unfortunately I think it only gets us
halfway there. It drops the extra space when folding this:

  printf 'Foo: bar\r\n'
  printf ' \r\n'
  printf ' baz\r\n'

which will yield:

  Foo: bar baz

and it fixes the first of Git's failing tests. But if we swap out the space for a tab
like this:

  printf 'Foo: bar\r\n'
  printf ' \r\n'
  printf '\tbaz\r\n'

then we get collapsed whitespace, but it's a tab. I.e.:

  Foo: bar\tbaz

(where "\t" is a literal tab). I think that does violate the standard
(which says it should become spaces). I think in most headers the
grammar allows OWS/RWS fields that are spaces or tabs, so in theory it
shouldn't matter. But I wouldn't be surprised if that causes some
surprises in the real world.

Sadly the input buffer to http_parse_headers() is const, so we can't
just write a space over the original tab. ;) But I think rather than
walking back to preserve that final leading whitespace byte, we could
just always add in our own space separately, like this:

diff --git a/lib/http.c b/lib/http.c
index ea62219542..eaa8bf73c2 100644
--- a/lib/http.c
+++ b/lib/http.c
@@ -4388,6 +4388,7 @@ static CURLcode http_parse_headers(struct Curl_easy *data,
     {
       /* preserve the whole original header piece size */
       size_t header_piece = consumed;
+      bool did_unfold = false;
 
       if(data->state.leading_unfold) {
         /* immediately after an unfold, keep only a single whitespace */
@@ -4398,17 +4399,18 @@ static CURLcode http_parse_headers(struct Curl_easy *data,
           blen--;
         }
         if(consumed) {
-          if(iblen > blen) {
-            /* take one step back */
-            consumed++;
-            buf--;
-            blen++;
-          }
           data->state.leading_unfold = FALSE; /* done now */
+          did_unfold = TRUE;
         }
       }
 
       if(consumed) {
+        if (did_unfold) {
+          result = curlx_dyn_addn(&data->state.headerb, " ", 1);
+          if(result)
+            return result;
+        }
+
         result = curlx_dyn_addn(&data->state.headerb, buf, consumed);
         if(result)
           return result;

-Peff