From 53e5d49314513aa40f04254b37554e2ffae89cf5 Mon Sep 17 00:00:00 2001 From: Edmond <1571649+EdmondDantes@users.noreply.github.com> Date: Sat, 6 Jun 2026 13:38:52 +0300 Subject: [PATCH] test(win): pass h2 conditional ETag via curl --config, not shell-quoted -H MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit static/012-static-h2 failed on Windows: the conditional GET (If-None-Match with the response ETag) came back as a stream PROTOCOL_ERROR instead of a clean response. The root cause is in the test, not the server. An ETag is `W/"hex"`, and PHP's escapeshellarg() on Windows cannot carry a double quote — it replaces every " with a space, so `If-None-Match: W/"hex"` reached curl as `W/ hex `. Leading/trailing whitespace in a field value is malformed per RFC 9113 §8.2.1, and the server's nghttp2 correctly rejects the stream with PROTOCOL_ERROR. On Linux escapeshellarg wraps args in single quotes, preserving the " — which is why this only failed on Windows. Fix (test-only): route -H request headers through a curl --config file, whose quoting is shell-independent, so the quoted ETag survives on every platform. Full coverage (304-via-If-None-Match, byte ranges, HEAD) is preserved; the server is unchanged and behaves correctly per spec. Verified on Windows: static/012-static-h2 red->green (3/3); full static/ suite 20 pass / 0 fail / 4 skip. --- tests/phpt/server/static/012-static-h2.phpt | 28 ++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/tests/phpt/server/static/012-static-h2.phpt b/tests/phpt/server/static/012-static-h2.phpt index 58927d7..eb79d46 100644 --- a/tests/phpt/server/static/012-static-h2.phpt +++ b/tests/phpt/server/static/012-static-h2.phpt @@ -58,11 +58,37 @@ $client = spawn(function() use ($port, $server, $body) { * separately so we can parse ":status" and Content-Length * cleanly even on 206 / 304. */ $args = ['--http2-prior-knowledge', '-sS', '-i', '--max-time', '3']; - foreach ($extra_args as $a) $args[] = $a; + + /* Route request headers through a curl --config file rather than + * shell-quoted -H. An ETag is `W/"hex"`, and PHP's escapeshellarg() + * on Windows cannot carry a double quote — it replaces every " with + * a space, so `If-None-Match: W/"hex"` would reach curl as + * `W/ hex `. Leading/trailing whitespace makes that a malformed + * HTTP/2 field value (RFC 9113 §8.2.1), which the server correctly + * rejects with a stream PROTOCOL_ERROR. curl config files use curl's + * own (shell-independent) quoting, so the quotes survive everywhere. */ + $rest = []; $cfg_lines = []; $cfg = null; + for ($i = 0, $n = count($extra_args); $i < $n; $i++) { + if ($extra_args[$i] === '-H' && $i + 1 < $n) { + $val = $extra_args[++$i]; + $esc = str_replace(['\\', '"'], ['\\\\', '\\"'], $val); + $cfg_lines[] = 'header = "' . $esc . '"'; + } else { + $rest[] = $extra_args[$i]; + } + } + if ($cfg_lines) { + $cfg = tempnam(sys_get_temp_dir(), 'h2cfg'); + file_put_contents($cfg, implode("\n", $cfg_lines) . "\n"); + $args[] = '--config'; + $args[] = $cfg; + } + foreach ($rest as $a) $args[] = $a; $args[] = "http://127.0.0.1:$port$path"; $cmd = 'curl ' . implode(' ', array_map('escapeshellarg', $args)); $out = []; $rc = 0; exec($cmd . ' 2>&1', $out, $rc); + if ($cfg !== null) @unlink($cfg); $resp = implode("\n", $out); /* Parse a curl -i response: header block ends at the blank * line; HTTP/2 status line is "HTTP/2 NNN". */