Skip to content

Commit

Permalink
vcl: Allow header names to be quoted
Browse files Browse the repository at this point in the history
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
  • Loading branch information
dridi committed Aug 17, 2021
1 parent dc7e849 commit c71ab01
Show file tree
Hide file tree
Showing 4 changed files with 102 additions and 13 deletions.
51 changes: 51 additions & 0 deletions bin/varnishtest/tests/b00076.vtc
Original file line number Diff line number Diff line change
@@ -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
10 changes: 7 additions & 3 deletions doc/sphinx/reference/vcl_var.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
41 changes: 31 additions & 10 deletions lib/libvcc/vcc_symb.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -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;
Expand All @@ -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;
}
Expand Down Expand Up @@ -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);
Expand Down
13 changes: 13 additions & 0 deletions lib/libvcc/vcc_var.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand All @@ -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;
Expand Down

0 comments on commit c71ab01

Please sign in to comment.