/* This file is part of vmod-variable
   Copyright (C) 2013-2017 Sergey Poznyakoff
  
   Vmod-variable 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-variable 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-variable.  If not, see <http://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdlib.h>
#include <stdarg.h>
#include <syslog.h>
#include <errno.h>
#include <ctype.h>
#include <pcre.h>
#include "vrt.h"
#include "vcc_if.h"
#include "pthread.h"
#include "cache/cache.h"

#define WSPTR(s) ((s)->ws)

/* |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;
	}

	if (!entry)
		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 *global_symtab;
static pthread_mutex_t global_mtx = PTHREAD_MUTEX_INITIALIZER;

VCL_VOID
vmod_global_set(VRT_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(VRT_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(VRT_CTX, VCL_STRING name)
{
	return !!vmod_global_get(ctx, name);
}

VCL_VOID
vmod_global_clear(VRT_CTX)
{
	AZ(pthread_mutex_lock(&global_mtx));
	symtab_clear(global_symtab);
	AZ(pthread_mutex_unlock(&global_mtx));
}

VCL_VOID
vmod_global_unset(VRT_CTX, VCL_STRING name)
{
	AZ(pthread_mutex_lock(&global_mtx));
	symtab_remove(global_symtab, name);
	AZ(pthread_mutex_unlock(&global_mtx));
}

static void
priv_symtab_free(void *p)
{
	symtab_free(p);
}

static struct symtab *
get_symtab(struct vmod_priv *priv)
{
	if (!priv->priv) {
		priv->priv = symtab_create();
		priv->free = priv_symtab_free;
	}
	return priv->priv;
}

#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(VRT_CTX, struct vmod_priv *priv)
{
	symtab_clear(get_symtab(priv));
}

VCL_STRING
vmod_get_string(VRT_CTX, struct vmod_priv *priv, VCL_STRING name)
{
	struct variable *var = getvar(get_symtab(priv), name);
	if (var && var->type == variable_string)
		return var->v.s;
	return NULL;
}

VCL_VOID
vmod_set_string(VRT_CTX, struct vmod_priv *priv,
		VCL_STRING name, VCL_STRING value)
{
	struct symtab *vt = get_symtab(priv);
	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(VRT_CTX, struct vmod_priv *priv, VCL_STRING name)
{
	return vmod_get_string(ctx, priv, name);
}

VCL_VOID
vmod_set(VRT_CTX, struct vmod_priv *priv,
	 VCL_STRING name, VCL_STRING value)
{
	vmod_set_string(ctx, priv, name, value);
}

#define __cat__(a,b) a ## b
#define DEFGET(r_type, vcl_type, memb)	                            \
vcl_type					                    \
__cat__(vmod_get_,r_type)(VRT_CTX, struct vmod_priv *priv,	    \
			  VCL_STRING name)			    \
{							            \
	struct variable *var = getvar(get_symtab(priv), 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)(VRT_CTX, struct vmod_priv *priv,	    \
			  VCL_STRING name,			    \
			  vcl_type value)			    \
{                                                                   \
	struct symtab *vt = get_symtab(priv);			       \
	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(VRT_CTX, struct vmod_priv *priv, VCL_STRING name)
{
	return !!getvar(get_symtab(priv), name);
}

VCL_STRING
vmod_type_of(VRT_CTX, struct vmod_priv *priv, VCL_STRING name)
{
	struct variable *var = getvar(get_symtab(priv), name);
	return typestr[var ? var->type : variable_unset];
}

VCL_VOID
vmod_unset(VRT_CTX, struct vmod_priv *priv, VCL_STRING name)
{
	symtab_remove(get_symtab(priv), 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(VRT_CTX, struct vmod_priv *priv,
	    VCL_STRING vars, VCL_STRING rxs, VCL_STRING input)
{
	struct symtab *vt = get_symtab(priv);
	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(VRT_CTX, struct vmod_priv *priv,
	      VCL_STRING vars, VCL_STRING query)
{
	struct symtab *vt = get_symtab(priv);
	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);
}
