/* This file is part of vmod-tbf
Copyright (C) 2013-2015 Sergey Poznyakoff
Vmod-tbf is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3, or (at your option)
any later version.
Vmod-tbf is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with vmod-tbf. If not, see .
*/
#include
#include
#include
#include
#include
#include
#include "vrt.h"
#include "vcc_if.h"
#include "pthread.h"
#if VARNISHVERSION == 3
# include "bin/varnishd/cache.h"
# define VCL_VOID void
# define VCL_STRING const char *
# define VCL_REAL double
# define VCL_DURATION double
# define WS_Copy(w,s,l) WS_Dup(w,s)
# define VARIABLE_CTX struct sess *
# define WSPTR(s) ((s)->wrk->ws)
#else
# include "bin/varnishd/cache/cache.h"
# define VARIABLE_CTX const struct vrt_ctx *
# define WSPTR(s) ((s)->ws)
#endif
/* |hash_size| defines a sequence of symbol table sizes. These are prime
numbers, each of which is approximately twice its predecessor. */
static unsigned int hash_size[] = {
7, 17, 37, 101, 229, 487, 1009, 2039, 4091, 8191, 16411
};
/* max_rehash keeps the number of entries in |hash_size| table. */
static unsigned int max_rehash = sizeof(hash_size) / sizeof(hash_size[0]);
enum variable_type {
variable_unset,
variable_string,
variable_int,
variable_real,
variable_duration
};
static char *typestr[] = {
"UNSET",
"STRING",
"INT",
"REAL",
"DURATION"
};
union value {
char *s;
int i;
double r;
double d;
};
struct variable {
char *name;
enum variable_type type;
union value v;
};
struct symtab {
uint32_t vxid;
unsigned int hash_num; /* Index to hash_size table */
struct variable **tab;
};
static struct variable *
var_alloc(const char *name)
{
struct variable *ent;
ent = malloc(sizeof(struct variable));
AN(ent);
ent->name = strdup(name);
ent->type = variable_unset;
AN(ent->name);
return ent;
}
static unsigned
hash_string(const char *name, unsigned long hashsize)
{
unsigned i;
for (i = 0; *name; name++) {
i <<= 1;
i ^= *(unsigned char*) name;
}
return i % hashsize;
}
static unsigned
var_hash(struct variable *var, unsigned long hashsize)
{
return hash_string(var->name, hashsize);
}
static void
var_free(struct variable *var)
{
free(var->name);
if (var->type == variable_string)
free(var->v.s);
free(var);
}
static unsigned
symtab_insert_pos(struct symtab *st, struct variable *elt)
{
unsigned i;
unsigned pos = var_hash(elt, hash_size[st->hash_num]);
for (i = pos; st->tab[i];) {
if (++i >= hash_size[st->hash_num])
i = 0;
if (i == pos)
abort();
}
return i;
}
static int
symtab_rehash(struct symtab *st)
{
struct variable **old_tab = st->tab;
struct variable **new_tab;
unsigned int i;
unsigned int hash_num = st->hash_num + 1;
if (hash_num >= max_rehash)
return E2BIG;
new_tab = calloc(hash_size[hash_num], sizeof(*new_tab));
AN(new_tab);
st->tab = new_tab;
if (old_tab) {
st->hash_num = hash_num;
for (i = 0; i < hash_size[hash_num-1]; i++) {
struct variable *elt = old_tab[i];
if (elt->name) {
unsigned n = symtab_insert_pos(st, elt);
new_tab[n] = elt;
}
}
free(old_tab);
}
return 0;
}
static int
symtab_remove(struct symtab *st, const char *name)
{
unsigned int pos, i, j, r;
struct variable *entry;
pos = hash_string(name, hash_size[st->hash_num]);
for (i = pos; (entry = st->tab[i]);) {
if (strcmp(entry->name, name) == 0)
break;
if (++i >= hash_size[st->hash_num])
i = 0;
if (i == pos)
return ENOENT;
}
var_free(entry);
for (;;) {
st->tab[i] = NULL;
j = i;
do {
if (++i >= hash_size[st->hash_num])
i = 0;
if (!st->tab[i])
return 0;
r = hash_string(st->tab[i]->name, hash_size[st->hash_num]);
}
while ((j < r && r <= i)
|| (i < j && j < r) || (r <= i && i < j));
st->tab[j] = st->tab[i];
}
return 0;
}
static int
symtab_get_index(unsigned *idx, struct symtab *st,
const char *name, int *install)
{
int rc;
unsigned i, pos;
struct variable *elem;
if (!st->tab) {
if (install) {
rc = symtab_rehash(st);
if (rc)
return rc;
} else
return ENOENT;
}
pos = hash_string(name, hash_size[st->hash_num]);
for (i = pos; (elem = st->tab[i]);) {
if (strcmp(elem->name, name) == 0) {
if (install)
*install = 0;
*idx = i;
return 0;
}
if (++i >= hash_size[st->hash_num])
i = 0;
if (i == pos)
break;
}
if (!install)
return ENOENT;
if (!elem) {
*install = 1;
*idx = i;
return 0;
}
if ((rc = symtab_rehash(st)) != 0)
return rc;
return symtab_get_index(idx, st, name, install);
}
static struct variable *
symtab_lookup_or_install(struct symtab *st, const char *name, int *install)
{
unsigned i;
int rc = symtab_get_index(&i, st, name, install);
if (rc == 0) {
if (install && *install == 1) {
struct variable *ent = var_alloc(name);
if (!ent) {
errno = ENOMEM;
return NULL;
}
st->tab[i] = ent;
return ent;
} else
return st->tab[i];
}
errno = rc;
return NULL;
}
static void
symtab_clear(struct symtab *st)
{
unsigned i, hs;
if (!st || !st->tab)
return;
hs = hash_size[st->hash_num];
for (i = 0; i < hs; i++) {
struct variable *elem = st->tab[i];
if (elem) {
var_free(elem);
st->tab[i] = NULL;
}
}
}
static struct symtab *
symtab_create()
{
struct symtab *st = calloc(1, sizeof(*st));
AN(st);
st->tab = calloc(hash_size[st->hash_num], sizeof(*st->tab));
AN(st->tab);
return st;
}
static void
symtab_free(struct symtab *st)
{
if (st) {
symtab_clear(st);
free(st->tab);
free(st);
}
}
static struct symtab **symtabv;
static size_t symtabc;
static pthread_mutex_t symtab_mtx = PTHREAD_MUTEX_INITIALIZER;
static struct symtab *global_symtab;
static pthread_mutex_t global_mtx = PTHREAD_MUTEX_INITIALIZER;
VCL_VOID
vmod_global_set(VARIABLE_CTX ctx, VCL_STRING name, VCL_STRING value)
{
struct variable *var;
int inst;
AZ(pthread_mutex_lock(&global_mtx));
if (!global_symtab)
global_symtab = symtab_create();
if (!value)
symtab_remove(global_symtab, name);
else {
inst = 1;
var = symtab_lookup_or_install(global_symtab, name, &inst);
if (!inst && var->type == variable_string)
free(var->v.s);
var->type = variable_string;
var->v.s = strdup(value);
AN(var->v.s);
}
AZ(pthread_mutex_unlock(&global_mtx));
}
VCL_STRING
vmod_global_get(VARIABLE_CTX ctx, VCL_STRING name)
{
char *s;
struct variable *var;
AZ(pthread_mutex_lock(&global_mtx));
if (!global_symtab)
global_symtab = symtab_create();
var = symtab_lookup_or_install(global_symtab, name, NULL);
if (var && var->type == variable_string) {
s = WS_Copy(ctx->ws, var->v.s, -1);
AN(s);
} else
s = NULL;
AZ(pthread_mutex_unlock(&global_mtx));
return s;
}
VCL_BOOL
vmod_global_defined(VARIABLE_CTX ctx, VCL_STRING name)
{
return !!vmod_global_get(ctx, name);
}
VCL_VOID
vmod_global_clear(VARIABLE_CTX ctx)
{
AZ(pthread_mutex_lock(&global_mtx));
symtab_clear(global_symtab);
AZ(pthread_mutex_unlock(&global_mtx));
}
VCL_VOID
vmod_global_unset(VARIABLE_CTX ctx, VCL_STRING name)
{
AZ(pthread_mutex_lock(&global_mtx));
symtab_remove(global_symtab, name);
AZ(pthread_mutex_unlock(&global_mtx));
}
static struct symtab *
get_symtab(VARIABLE_CTX ctx)
{
struct symtab *st;
int fd = ctx->req->sp->fd;
assert(fd >= 0);
AZ(pthread_mutex_lock(&symtab_mtx));
if (symtabc <= fd) {
size_t n = fd + 1;
symtabv = realloc(symtabv, n * sizeof(symtabv[0]));
while (symtabc < n)
symtabv[symtabc++] = NULL;
}
if (!symtabv[fd])
symtabv[fd] = symtab_create();
st = symtabv[fd];
if (st->vxid != ctx->req->sp->vxid)
symtab_clear(st);
st->vxid = ctx->req->sp->vxid;
AZ(pthread_mutex_unlock(&symtab_mtx));
return st;
}
#define getvar(vt, name) symtab_lookup_or_install(vt, name, NULL)
static struct variable *
defvar(struct symtab *vt, const char *name, enum variable_type t,
union value *val)
{
int inst = 1;
struct variable *var = symtab_lookup_or_install(vt, name, &inst);
if (!inst && var->type == variable_string)
free(var->v.s);
if (t != variable_unset && val) {
var->type = t;
var->v = *val;
} else
var->type = variable_unset;
return var;
}
VCL_VOID
vmod_clear(VARIABLE_CTX ctx)
{
symtab_clear(get_symtab(ctx));
}
VCL_STRING
vmod_get_string(VARIABLE_CTX ctx, VCL_STRING name)
{
struct variable *var = getvar(get_symtab(ctx), name);
if (var && var->type == variable_string)
return var->v.s;
return NULL;
}
VCL_VOID
vmod_set_string(VARIABLE_CTX ctx, VCL_STRING name, VCL_STRING value)
{
struct symtab *vt = get_symtab(ctx);
struct variable *var = defvar(vt, name, variable_unset, NULL);
var->type = variable_string;
var->v.s = strdup(value ? value : "");
AN(var->v.s);
}
VCL_STRING
vmod_get(VARIABLE_CTX ctx, VCL_STRING name)
{
return vmod_get_string(ctx, name);
}
VCL_VOID
vmod_set(VARIABLE_CTX ctx, VCL_STRING name, VCL_STRING value)
{
vmod_set_string(ctx, name, value);
}
#define __cat__(a,b) a ## b
#define DEFGET(r_type, vcl_type, memb) \
vcl_type \
__cat__(vmod_get_,r_type)(VARIABLE_CTX ctx, VCL_STRING name) \
{ \
struct variable *var = getvar(get_symtab(ctx), name); \
if (var && var->type == __cat__(variable_,r_type)) \
return var->v.memb; \
return 0; \
}
#define DEFSET(r_type, vcl_type, memb) \
VCL_VOID \
__cat__(vmod_set_,r_type)(VARIABLE_CTX ctx, VCL_STRING name, \
vcl_type value) \
{ \
struct symtab *vt = get_symtab(ctx); \
struct variable *var = defvar(vt, name, variable_unset, NULL); \
var->type = __cat__(variable_,r_type); \
var->v.memb = value; \
}
#define DEF(name, vcl_type, memb) \
DEFGET(name, vcl_type, memb) \
DEFSET(name, vcl_type, memb)
DEF(int, VCL_INT, i)
DEF(real, VCL_REAL, r)
DEF(duration, VCL_DURATION, d)
VCL_BOOL
vmod_defined(VARIABLE_CTX ctx, VCL_STRING name)
{
return !!getvar(get_symtab(ctx), name);
}
VCL_STRING
vmod_type_of(VARIABLE_CTX ctx, VCL_STRING name)
{
struct variable *var = getvar(get_symtab(ctx), name);
return typestr[var ? var->type : variable_unset];
}
VCL_VOID
vmod_unset(VARIABLE_CTX ctx, VCL_STRING name)
{
symtab_remove(get_symtab(ctx), name);
}
static void
log_error(const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
vsyslog(LOG_DAEMON|LOG_NOTICE, fmt, ap);
va_end(ap);
}
#define S(s) ((s) ? (s) : "NULL")
struct vardef {
struct vardef *next;
enum variable_type type;
char *name;
char *repl;
};
static struct vardef *
vardef_new(enum variable_type type,
char const *nameptr, size_t namelen,
char const *replptr, size_t repllen)
{
struct vardef *def = malloc(sizeof(def[0]) + namelen + repllen + 2);
def->next = NULL;
def->type = type;
def->name = (char*)(def + 1);
memcpy(def->name, nameptr, namelen);
def->name[namelen] = 0;
if (replptr) {
def->repl = def->name + namelen + 1;
memcpy(def->repl, replptr, repllen);
def->repl[repllen] = 0;
} else
def->repl = NULL;
return def;
}
static void
vardef_free(struct vardef *v)
{
while (v) {
struct vardef *n = v->next;
free(v);
v = n;
}
}
static struct vardef *
vardef_find(struct vardef *v, const char *name, size_t len)
{
for (; v; v = v->next)
if (strlen(v->name) == len && memcmp(v->name, name, len) == 0)
break;
return v;
}
static enum variable_type
str2type(const char *s, size_t len)
{
int i;
for (i = 0; i < sizeof(typestr)/sizeof(typestr[0]); i++) {
if (strlen(typestr[i]) < len)
continue;
if (strncasecmp(typestr[i], s, len) == 0)
return i;
}
return variable_unset;
}
static double
str2duration(const char *str)
{
char *p;
double r;
errno = 0;
r = strtod(str, &p);
if (errno)
return 0.0;
while (*p && isspace(*p))
p++;
switch (*p++) {
case 's':
break;
case 'm':
if (*p == 's') {
r *= 1e-3;
p++;
} else
r *= 60.;
break;
case 'h':
r *= 60.*60.;
break;
case 'd':
r *= 60.*60.*24.;
break;
case 'w':
r *= 60.*60.*24.*7.;
break;
case 'y':
r *= 60.*60.*24.*365.;
break;
default:
return 0.0;
}
while (*p && isspace(*p))
p++;
return *p ? 0 : r;
}
static char *
bref_expand(const char *str, const char *input, pcre *re,
int ovsize, int *ovector)
{
size_t rlen = 0;
char *rbase = NULL;
char *rptr;
int nm = 2*ovsize/3;
const char *p;
for (p = str; *p; ) {
if (*p == '\\' && strchr("123456789", p[1])) {
int n = 2*(p[1] - '0');
if (n < nm) {
rlen += ovector[n+1] - ovector[n];
p += 2;
continue;
}
}
++p;
++rlen;
}
rbase = malloc(rlen + 1);
AN(rbase);
p = str;
rptr = rbase;
while (*p) {
if (*p == '\\' && strchr("123456789", p[1])) {
int n = 2*(p[1] - '0');
if (n < nm) {
memcpy(rptr, input + ovector[n],
ovector[n+1] - ovector[n]);
rptr += ovector[n+1] - ovector[n];
p += 2;
continue;
}
}
*rptr++ = *p++;
}
*rptr = 0;
return rbase;
}
static void
setval(union value *val, const char *s, enum variable_type type, char **err)
{
char *p;
long lval;
*err = NULL;
switch (type) {
case variable_string:
val->s = strdup(s);
AN(val->s);
break;
case variable_int:
errno = 0;
lval = strtol(s, &p, 10);
if (*p) {
*err = "not an integer";
val->i = 0;
} else if (errno) {
*err = strerror(errno);
val->i = 0;
} else if (lval < INT_MIN || lval > INT_MAX) {
*err = "value out of range";
val->i = 0;
} else
val->i = lval;
break;
case variable_real:
errno = 0;
val->r = strtod(s, &p);
if (*p) {
*err = "not a valid number";
val->r = 0;
} else if (errno) {
*err = strerror(errno);
val->r = 0;
}
break;
case variable_duration:
val->d = str2duration(s);
break;
default:
abort();
}
}
VCL_VOID
vmod_regset(VARIABLE_CTX ctx, VCL_STRING vars, VCL_STRING rxs,
VCL_STRING input)
{
struct symtab *vt = get_symtab(ctx);
struct vardef *head = NULL, *tail = NULL, *def;
size_t count = 0;
size_t n;
const char *v = vars;
const char *error_ptr;
int error_offset;
int cflags = 0;
union value value;
int ovsize;
int *ovector;
int i;
int rc;
pcre *re;
if (!vars || !rxs || !input) {
log_error("variable.regset: bad arguments: vars=%s, rxs=%s, input=%s",
S(vars), S(rxs), S(input));
return;
}
while (*v) {
char const *nameptr = v;
size_t n = strcspn(v, ":=,");
size_t namelen = n;
char const *replptr;
size_t repllen;
char rbuf[3];
enum variable_type type;
int delim;
v += n;
delim = *v ? *v++ : 0;
if (delim == ':') {
n = strcspn(v, "=,");
type = str2type(v, n);
v += n;
delim = *v ? *v++ : 0;
} else
type = variable_string;
if (delim == '=') {
n = strcspn(v, ",");
replptr = v;
repllen = n;
v += n;
delim = *v ? *v++ : 0;
} else {
rbuf[0] = '\\';
rbuf[1] = count + '1';
rbuf[2] = 0;
replptr = rbuf;
repllen = 2;
}
def = vardef_new(type, nameptr, namelen, replptr, repllen);
if (tail)
tail->next = def;
else
head = def;
tail = def;
++count;
}
re = pcre_compile(rxs, cflags, &error_ptr, &error_offset, NULL);
if (!re) {
log_error("variable.regset: %s: compilation failed near %s: %s",
rxs, rxs + error_offset, error_ptr);
vardef_free(head);
return;
}
rc = pcre_fullinfo(re, NULL, PCRE_INFO_CAPTURECOUNT, &n);
if (rc) {
log_error("pcre_fullinfo() failed: %d", rc);
vardef_free(head);
return;
}
if (n < count) {
log_error("variable.regset: %s: too few subexpressions to satisfy %s",
rxs, vars);
vardef_free(head);
return;
}
ovsize = (n + 1) * 3;
ovector = calloc(ovsize, sizeof(*ovector));
rc = pcre_exec(re, 0, input, strlen(input), 0, 0, ovector, ovsize);
if (rc <= 0) {
if (rc != PCRE_ERROR_NOMATCH)
log_error("variable.regset: pcre_exec failed: %d", rc);
vardef_free(head);
return;
}
for (def = head; def; def = def->next, i++) {
char *s = bref_expand(def->repl, input, re, ovsize, ovector);
char *err;
setval(&value, s, def->type, &err);
if (err)
log_error("variable.regset: %s(%s)#%d: %s",
rxs, input, i, err);
defvar(vt, def->name, def->type, &value);
free(s);
}
pcre_free(re);
free(ovector);
vardef_free(head);
}
static unsigned long
hex2ul(char hex)
{
if (hex >= '0' && hex <= '9')
return hex - '0';
if (hex >= 'a' && hex <= 'z')
return hex - 'a' + 10;
if (hex >= 'A' && hex <= 'Z')
return hex - 'A' + 10;
return -1;
}
/* From RFC 1738, section 2.2 */
static void
xdecode(char *s)
{
char *d;
d = strchr(s, '%');
if (!d)
return;
for (s = d; *s; ) {
if (*s == '%' && hex2ul(s[1]) != -1 && hex2ul(s[2]) != -1) {
*d++ = (hex2ul(s[1]) << 4) + hex2ul(s[2]);
s += 3;
} else
*d++ = *s++;
}
*d = 0;
}
static void
define_param(struct symtab *vt, struct vardef *def,
char const *valptr, size_t vallen)
{
char *s;
char *err;
union value value;
s = malloc(vallen+1);
AN(s);
memcpy(s, valptr, vallen);
s[vallen] = 0;
xdecode(s);
setval(&value, s, def->type, &err);
if (err)
log_error("variable.queryset: %s", err);
defvar(vt, def->name, def->type, &value);
free(s);
}
VCL_VOID
vmod_queryset(VARIABLE_CTX ctx, VCL_STRING vars, VCL_STRING query)
{
struct symtab *vt = get_symtab(ctx);
struct vardef *head = NULL, *tail = NULL, *def;
size_t count = 0;
const char *v = vars;
while (*v) {
char const *nameptr = v;
size_t n = strcspn(v, ":,");
size_t namelen = n;
enum variable_type type;
int delim;
v += n;
delim = *v ? *v++ : 0;
if (delim == ':') {
n = strcspn(v, ",");
type = str2type(v, n);
v += n;
delim = *v ? *v++ : 0;
} else
type = variable_string;
def = vardef_new(type, nameptr, namelen, NULL, 0);
if (tail)
tail->next = def;
else
head = def;
tail = def;
++count;
}
if (!query || !*query) {
for (def = head; def; def = def->next)
symtab_remove(vt, def->name);
vardef_free(head);
return;
}
v = query;
while (*v) {
char const *paramptr = v;
size_t n = strcspn(v, "=&");
size_t paramlen = n;
char const *valptr = NULL;
size_t vallen = 0;
int delim;
v += n;
delim = *v ? *v++ : 0;
if (delim == '=') {
n = strcspn(v, "&");
valptr = v;
vallen = n;
v += n;
delim = *v ? *v++ : 0;
}
if (head) {
def = vardef_find(head, paramptr, paramlen);
if (def)
define_param(vt, def, valptr, vallen);
} else {
def = vardef_new(variable_string, paramptr, paramlen,
NULL, 0);
define_param(vt, def, valptr, vallen);
vardef_free(def);
}
}
vardef_free(head);
}