MEDIUM: http-ana: Add a proxy option to restrict chars in request header names

The "http-restrict-req-hdr-names" option can now be set to restrict allowed
characters in the request header names to the "[a-zA-Z0-9-]" charset.

Idea of this option is to not send header names with non-alphanumeric or
hyphen character. It is especially important for FastCGI application because
all those characters are converted to underscore. For instance,
"X-Forwarded-For" and "X_Forwarded_For" are both converted to
"HTTP_X_FORWARDED_FOR". So, header names can be mixed up by FastCGI
applications. And some HAProxy rules may be bypassed by mangling header
names. In addition, some non-HTTP compliant servers may incorrectly handle
requests when header names contain characters ouside the "[a-zA-Z0-9-]"
charset.

When this option is set, the policy must be specify:

  * preserve: It disables the filtering. It is the default mode for HTTP
              proxies with no FastCGI application configured.

  * delete: It removes request headers with a name containing a character
            outside the "[a-zA-Z0-9-]" charset. It is the default mode for
            HTTP backends with a configured FastCGI application.

  * reject: It rejects the request with a 403-Forbidden response if it
            contains a header name with a character outside the
            "[a-zA-Z0-9-]" charset.

The option is evaluated per-proxy and after http-request rules evaluation.

This patch may be backported to avoid any secuirty issue with FastCGI
application (so as far as 2.2).

(cherry picked from commit 18c13d3bd88cbcc351a61b1e71881353ab720f67)
Signed-off-by: Christopher Faulet <cfaulet@haproxy.com>
(cherry picked from commit bf65f308da8b2e6d82d2fb2b242d4bb8f82778d0)
Signed-off-by: Christopher Faulet <cfaulet@haproxy.com>
diff --git a/reg-tests/http-rules/restrict_req_hdr_names.vtc b/reg-tests/http-rules/restrict_req_hdr_names.vtc
new file mode 100644
index 0000000..28a10d3
--- /dev/null
+++ b/reg-tests/http-rules/restrict_req_hdr_names.vtc
@@ -0,0 +1,123 @@
+varnishtest "http-restrict-req-hdr-names option tests"
+#REQUIRE_VERSION=2.6
+
+# This config tests "http-restrict-req-hdr-names" option
+
+feature ignore_unknown_macro
+
+server s1 {
+    rxreq
+    expect req.http.x-my_hdr  == on
+    txresp
+} -start
+
+server s2 {
+    rxreq
+    expect req.http.x-my_hdr  == <undef>
+    txresp
+} -start
+
+server s3 {
+    rxreq
+    expect req.http.x-my_hdr  == on
+    txresp
+} -start
+
+server s4 {
+    rxreq
+    expect req.http.x-my_hdr  == <undef>
+    txresp
+} -start
+
+server s5 {
+    rxreq
+    expect req.http.x-my_hdr  == on
+    txresp
+} -start
+
+haproxy h1 -conf {
+    defaults
+        mode http
+        timeout connect "${HAPROXY_TEST_TIMEOUT-5s}"
+        timeout client  "${HAPROXY_TEST_TIMEOUT-5s}"
+        timeout server  "${HAPROXY_TEST_TIMEOUT-5s}"
+
+    frontend fe1
+        bind "fd@${fe1}"
+        use_backend be-http1 if { path /req1 }
+        use_backend be-http2 if { path /req2 }
+        use_backend be-http3 if { path /req3 }
+        use_backend be-fcgi1 if { path /req4 }
+        use_backend be-fcgi2 if { path /req5 }
+        use_backend be-fcgi3 if { path /req6 }
+
+    backend be-http1
+        server s1 ${s1_addr}:${s1_port}
+
+    backend be-http2
+        option http-restrict-req-hdr-names delete
+        server s2 ${s2_addr}:${s2_port}
+
+    backend be-http3
+        option http-restrict-req-hdr-names reject
+
+    backend be-fcgi1
+        option http-restrict-req-hdr-names preserve
+        server s3 ${s3_addr}:${s3_port}
+
+    backend be-fcgi2
+        option http-restrict-req-hdr-names delete
+        server s4 ${s4_addr}:${s4_port}
+
+    backend be-fcgi3
+        option http-restrict-req-hdr-names reject
+
+    defaults
+        mode http
+        timeout connect "${HAPROXY_TEST_TIMEOUT-5s}"
+        timeout client  "${HAPROXY_TEST_TIMEOUT-5s}"
+        timeout server  "${HAPROXY_TEST_TIMEOUT-5s}"
+        option http-restrict-req-hdr-names preserve
+
+    frontend fe2
+        bind "fd@${fe2}"
+        default_backend be-fcgi4
+
+    backend be-fcgi4
+        server s5 ${s5_addr}:${s5_port}
+
+    fcgi-app my-fcgi-app
+        docroot ${testdir}
+} -start
+
+client c1 -connect ${h1_fe1_sock} {
+    txreq -req GET -url /req1 -hdr "X-my_hdr: on"
+    rxresp
+    expect resp.status == 200
+
+    txreq -req GET -url /req2 -hdr "X-my_hdr: on"
+    rxresp
+    expect resp.status == 200
+
+    txreq -req GET -url /req3 -hdr "X-my_hdr: on"
+    rxresp
+    expect resp.status == 403
+
+    txreq -req GET -url /req4 -hdr "X-my_hdr: on"
+    rxresp
+    expect resp.status == 200
+
+    txreq -req GET -url /req5 -hdr "X-my_hdr: on"
+    rxresp
+    expect resp.status == 200
+
+    txreq -req GET -url /req6 -hdr "X-my_hdr: on"
+    rxresp
+    expect resp.status == 403
+} -run
+
+client c2 -connect ${h1_fe2_sock} {
+    txreq -req GET -url /req1 -hdr "X-my_hdr: on"
+    rxresp
+    expect resp.status == 200
+} -run