From 89a3c4f713e6acc48093f6864d6a4a8c4627bdd1 Mon Sep 17 00:00:00 2001 From: Dridi Boukelmoune Date: Wed, 30 Jun 2021 14:31:22 +0200 Subject: [PATCH] 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 --- bin/varnishtest/tests/b00076.vtc | 44 ++++++++++++++++++++++++++++++++ doc/sphinx/reference/vcl_var.rst | 10 +++++--- lib/libvcc/vcc_symb.c | 29 ++++++++++++++------- lib/libvcc/vcc_var.c | 13 ++++++++++ 4 files changed, 84 insertions(+), 12 deletions(-) create mode 100644 bin/varnishtest/tests/b00076.vtc diff --git a/bin/varnishtest/tests/b00076.vtc b/bin/varnishtest/tests/b00076.vtc new file mode 100644 index 00000000000..fde848a8def --- /dev/null +++ b/bin/varnishtest/tests/b00076.vtc @@ -0,0 +1,44 @@ +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; + } +} + +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 af4c7ecdf08..cce173636c1 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 6c7c182d7b3..45ca0486825 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,7 +211,8 @@ 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)) @@ -285,8 +289,13 @@ 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 == 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 +306,9 @@ 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->wildcard == NULL) + break; + if (tn1->tok != CSTR && tn1->tok != ID) break; tn = tn1; } @@ -421,7 +432,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 cdc071f9fb0..c71831bd64c 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;