[master] c71ab01e0 vcl: Allow header names to be quoted

Dridi Boukelmoune dridi.boukelmoune at gmail.com
Tue Aug 17 09:05:06 UTC 2021


commit c71ab01e054a896448326977769c811e59d0dddf
Author: Dridi Boukelmoune <dridi.boukelmoune at gmail.com>
Date:   Wed Jun 30 14:31:22 2021 +0200

    vcl: Allow header names to be quoted
    
    Because we funnel HTTP header names through the symbol table they have
    to be valid VCL identifiers. It means that we can't support all valid
    header names, which are tokens in the HTTP grammar. To finally close this
    loophole without the help of a VMOD we allow header names to be quoted:
    
        req.http.regular-header
        req.http."quoted.header"
    
    However we don't want to allow any component of a symbol to be quoted:
    
        req."http".we-dont-want-this
    
    So we teach the symbol table that wildcard symbols may be quoted. There
    used to be several use cases for wildcards but it is now limited to HTTP
    headers.
    
    Refs #3246
    Refs #3379

diff --git a/bin/varnishtest/tests/b00076.vtc b/bin/varnishtest/tests/b00076.vtc
new file mode 100644
index 000000000..dbb01e81f
--- /dev/null
+++ b/bin/varnishtest/tests/b00076.vtc
@@ -0,0 +1,51 @@
+varnishtest "Non-symbolic HTTP headers names"
+
+varnish v1 -errvcl "Invalid character '\\n' in header name" {
+	backend be none;
+	sub vcl_recv {
+		set req.http.{"line
+break"} = "invalid";
+	}
+}
+
+varnish v1 -errvcl "Expected '=' got '.'" {
+	backend be none;
+	sub vcl_recv {
+		set req."http".wrong-quote = "invalid";
+	}
+}
+
+varnish v1 -syntax 4.0 -errvcl "Quoted headers are available for VCL >= 4.1" {
+	backend be none;
+	sub vcl_recv {
+		set req.http."quoted" = "invalid";
+	}
+}
+
+varnish v1 -vcl {
+	import std;
+	backend be none;
+	sub vcl_recv {
+		std.collect(req.http."...");
+		return (synth(200));
+	}
+	sub vcl_synth {
+		set resp.http."123" = "456";
+		set resp.http."456" = resp.http."123";
+		set resp.http.{"!!!"} = "???";
+		set resp.http."""resp.http.foo""" = "bar";
+		set resp.http.bar = resp.http."resp.http.foo".upper();
+		set resp.http."..." = req.http."...";
+	}
+} -start
+
+client c1 {
+	txreq -hdr "...: a" -hdr "...: b"
+	rxresp
+	expect resp.http.123 == 456
+	expect resp.http.456 == 456
+	expect resp.http.!!! == ???
+	expect resp.http.resp.http.foo == bar
+	expect resp.http.bar == BAR
+	expect resp.http.... == "a, b"
+} -run
diff --git a/doc/sphinx/reference/vcl_var.rst b/doc/sphinx/reference/vcl_var.rst
index af4c7ecdf..cce173636 100644
--- a/doc/sphinx/reference/vcl_var.rst
+++ b/doc/sphinx/reference/vcl_var.rst
@@ -225,11 +225,15 @@ req.http.*
 	The headers of request, things like ``req.http.date``.
 
 	The RFCs allow multiple headers with the same name, and both
-	``set`` and ``unset`` will remove *all* headers with the name given.
+	``set`` and ``unset`` will remove *all* headers with the name
+	given.
 
 	The header name ``*`` is a VCL symbol and as such cannot, for
-	example, start with a numeral. Custom VMODs exist for handling
-	of such header names.
+	example, start with a numeral. To work with valid header that
+	can't be represented as VCL symbols it is possible to quote the
+	name, like ``req.http."grammatically.valid"``. None of the HTTP
+	headers present in IANA registries need to be quoted, so the
+	quoted syntax is discouraged but available for interoperability.
 
 
 req.restarts
diff --git a/lib/libvcc/vcc_symb.c b/lib/libvcc/vcc_symb.c
index 6c7c182d7..703553f27 100644
--- a/lib/libvcc/vcc_symb.c
+++ b/lib/libvcc/vcc_symb.c
@@ -141,21 +141,24 @@ vcc_symtab_new(const char *name)
 }
 
 static struct symtab *
-vcc_symtab_str(struct symtab *st, const char *b, const char *e)
+vcc_symtab_str(struct symtab *st, const char *b, const char *e, unsigned tok)
 {
 	struct symtab *st2, *st3;
 	size_t l;
 	int i;
 	const char *q;
 
+	assert(tok == ID || tok == CSTR);
 	if (e == NULL)
 		e = strchr(b, '\0');
+	q = e;
 
 	while (b < e) {
-		for (q = b; q < e && *q != '.'; q++)
-			continue;
-		AN(q);
-		l = q - b;
+		if (tok == ID) {
+			for (q = b; q < e && *q != '.'; q++)
+				continue;
+		}
+		l = pdiff(b, q);
 		VTAILQ_FOREACH(st2, &st->children, list) {
 			i = strncasecmp(st2->name, b, l);
 			if (i < 0)
@@ -208,10 +211,12 @@ vcc_sym_in_tab(struct vcc *tl, struct symtab *st,
 	VTAILQ_FOREACH(sym, &st->symbols, list) {
 		if (sym->lorev > vhi || sym->hirev < vlo)
 			continue;
-		if ((kind == SYM_NONE && kind == sym->kind))
+		if (kind == SYM_NONE && kind == sym->kind &&
+		    sym->wildcard == NULL)
 			continue;
 		if (tl->syntax < VCL_41 && strcmp(sym->name, "default") &&
-		     (kind != SYM_NONE && kind != sym->kind))
+		     kind != SYM_NONE && kind != sym->kind &&
+		     sym->wildcard == NULL)
 			continue;
 		return (sym);
 	}
@@ -285,8 +290,20 @@ VCC_SymbolGet(struct vcc *tl, vcc_ns_t ns, vcc_kind_t kind,
 	st = tl->syms[ns->id];
 	t0 = tl->t;
 	tn = tl->t;
+	assert(tn->tok == ID);
 	while (1) {
-		st = vcc_symtab_str(st, tn->b, tn->e);
+		assert(tn->tok == ID || tn->tok == CSTR);
+		if (tn->tok == CSTR && tl->syntax < VCL_41) {
+			VSB_cat(tl->sb,
+			    "Quoted headers are available for VCL >= 4.1.\n"
+			    "At:");
+			vcc_ErrWhere(tl, tn);
+			return (NULL);
+		}
+		if (tn->tok == ID)
+			st = vcc_symtab_str(st, tn->b, tn->e, tn->tok);
+		else
+			st = vcc_symtab_str(st, tn->dec, NULL, tn->tok);
 		sym2 = vcc_sym_in_tab(tl, st, kind, tl->syntax, tl->syntax);
 		if (sym2 != NULL) {
 			sym = sym2;
@@ -297,7 +314,11 @@ VCC_SymbolGet(struct vcc *tl, vcc_ns_t ns, vcc_kind_t kind,
 		if (tn1->tok != '.')
 			break;
 		tn1 = vcc_PeekTokenFrom(tl, tn1);
-		if (tn1->tok != ID)
+		if (tn1->tok == CSTR && sym == NULL)
+			break;
+		if (tn1->tok == CSTR && sym->wildcard == NULL)
+			break;
+		if (tn1->tok != CSTR && tn1->tok != ID)
 			break;
 		tn = tn1;
 	}
@@ -421,7 +442,7 @@ VCC_MkSym(struct vcc *tl, const char *b, vcc_ns_t ns, vcc_kind_t kind,
 
 	if (tl->syms[ns->id] == NULL)
 		tl->syms[ns->id] = vcc_symtab_new("");
-	st = vcc_symtab_str(tl->syms[ns->id], b, NULL);
+	st = vcc_symtab_str(tl->syms[ns->id], b, NULL, ID);
 	AN(st);
 	sym = vcc_sym_in_tab(tl, st, kind, vlo, vhi);
 	AZ(sym);
diff --git a/lib/libvcc/vcc_var.c b/lib/libvcc/vcc_var.c
index cdc071f9f..c71831bd6 100644
--- a/lib/libvcc/vcc_var.c
+++ b/lib/libvcc/vcc_var.c
@@ -36,12 +36,15 @@
 
 #include "vcc_compile.h"
 
+#include "vct.h"
+
 /*--------------------------------------------------------------------*/
 
 void v_matchproto_(sym_wildcard_t)
 vcc_Var_Wildcard(struct vcc *tl, struct symbol *parent, struct symbol *sym)
 {
 	struct vsb *vsb;
+	const char *p;
 
 	assert(parent->type == HEADER);
 
@@ -52,6 +55,16 @@ vcc_Var_Wildcard(struct vcc *tl, struct symbol *parent, struct symbol *sym)
 		return;
 	}
 
+	for (p = sym->name; *p != '\0'; p++) {
+		if (!vct_istchar(*p)) {
+			VSB_cat(tl->sb, "Invalid character '");
+			VSB_quote(tl->sb, p, 1, VSB_QUOTE_PLAIN);
+			VSB_cat(tl->sb, "' in header name.\n");
+			tl->err = 1;
+			return;
+		}
+	}
+
 	AN(sym);
 	sym->noref = 1;
 	sym->kind = SYM_VAR;


More information about the varnish-commit mailing list