/*
 * Copyright (c) 2003-2011
 * Distributed Systems Software.  All rights reserved.
 * See the file LICENSE for redistribution information.
 */

/*
 * Support for variables
 * A variable reference, several of which may be embedded in a string, looks
 * like: ${[namespace::]varname[:flags]}, where an optional namespace qualifies
 * varname and <flags> is one or more alphabetic characters.
 * The meaning of the namespace and flags depends on the particular
 * context, which is supplied by the caller of these functions;
 * either may be NULL ("${::foo}" is legal and equivalent to "${foo}").
 * For example, an "i" flag may mean that the variable name should be
 * looked up in a case-independent fashion.
 * A variable name and a namespace name consists of any number of underscores
 * and alphanumerics.
 */

#include "local.h"

#ifndef lint
static const char copyright[] =
"Copyright (c) 2003-2011\n\
Distributed Systems Software.  All rights reserved.";
static const char revid[] =
  "$Id: var.c 2528 2011-09-23 21:54:05Z brachman $";
#endif

static const char *log_module_name = "var";

int
var_ns_is_valid_namespace_name(char *str)
{
  char *n, *p;

  if (str == NULL) {
#ifdef DEFAULT_NAMESPACE
	n = DEFAULT_NAMESPACE;
#else
	return(0);
#endif
  }
  else
	n = str;

  if (!isalpha((int) n[0]))
	return(0);

  for (p = n + 1; *p != '\0'; p++) {
	if (!isalpha((int) *p) && !isdigit((int) *p) && *p != '-' && *p != '_')
	  return(0);
  }

  return(1);
}

/*
 * It's desirable to directly refer to any CGI parameter by name within
 * a query component.  If variable names were limited to alphanumerics,
 * for example, then the perfectly valid CGI parameter name "-a!" could
 * not be referenced as ${-a!}; there would need to be a somewhat more
 * complicated syntax (e.g., "${'-a!'}).
 * Section 2 and Appendix A of RFC 2396 defines the URI and query string
 * character sets.  Valid characters within the query string (CGI parameters
 * are extracted from the query string) are the alphanumerics, "-", "_", ".",
 * "!", "~", "*", "'", "(", and ")", and also escaped characters, "%-hex-hex".
 *
 * The characters ";", "/", "?", ":", "@", "&", "=", "+", ",", and "$" are
 * reserved within the query string and the characters "{", "}", "|", "\", "^",
 * "[", "]", and "`" are forbidden everywhere, as are the set of characters
 * described in Section 2.4.3 (such as spaces, double quotes, angle brackets).
 */
int
var_ns_is_valid_varname(char *str, char **endp)
{
  char *p;
  static char *other_legal_chars = "-_.!~*'()";

  p = str;
  while (*p != '\0') {
	if (*p == '%') {
	  p++;
	  if (!isxdigit((int) *p))
		goto fail;
	  p++;
	  if (!isxdigit((int) *p))
		goto fail;
	}
	else if (*p == '#' && p == str && *(p + 1) == '\0')
	  ;
	else if (!isalnum((int) *p) && strchr(other_legal_chars, (int) *p) == NULL)
	  goto fail;
	p++;
  }

  if (p == str)
	goto fail;

  if (endp != NULL)
	*endp = p;
  return(1);

 fail:
  if (endp != NULL)
	*endp = p;
  return(0);
}

static int
var_parse_flags(Var *var)
{
  char *p;

  for (p = var->flagstr; p != NULL && *p != '\0'; p++) {
	switch ((int) *p) {
	case 'e':
	  var->flags |= VAR_EXISTS_FLAG;
	  break;

	case 'i':
	  var->flags |= VAR_ICASE_FLAG;
	  break;

	case 'n':
	  if (var->flags
		  & (VAR_EXISTS_FLAG | VAR_EMPTY_FLAG | VAR_ALTVAL_FLAG
			 | VAR_NALTVAL_FLAG)) {
		log_msg((LOG_ERROR_LEVEL, "Invalid variable modifier flags: %s",
				 var->flagstr));
		return(-1);
	  }

	  var->flags |= VAR_NONEMPTY_FLAG;
	  break;

	case 'z':
	  if (var->flags
		  & (VAR_EXISTS_FLAG | VAR_NONEMPTY_FLAG | VAR_ALTVAL_FLAG
			 | VAR_NALTVAL_FLAG)) {
		log_msg((LOG_ERROR_LEVEL, "Invalid variable modifier flags: %s",
				 var->flagstr));
		return(-1);
	  }

	  var->flags |= VAR_EMPTY_FLAG;
	  break;

	case '?':
	  if (var->flags
		  & (VAR_EXISTS_FLAG | VAR_NONEMPTY_FLAG | VAR_EMPTY_FLAG
			 | VAR_NALTVAL_FLAG)) {
		log_msg((LOG_ERROR_LEVEL, "Invalid variable modifier flags: %s",
				 var->flagstr));
		return(-1);
	  }
	  var->flags |= VAR_ALTVAL_FLAG;
	  var->altval = p + 1;
	  return(0);
	  /*NOTREACHED*/

	case '+':
	  if (var->flags
		  & (VAR_EXISTS_FLAG | VAR_NONEMPTY_FLAG | VAR_EMPTY_FLAG
			 | VAR_ALTVAL_FLAG)) {
		log_msg((LOG_ERROR_LEVEL, "Invalid variable modifier flags: %s",
				 var->flagstr));
		return(-1);
	  }
	  var->flags |= VAR_NALTVAL_FLAG;
	  var->altval = p + 1;
	  return(0);
	  /*NOTREACHED*/

	default:
	  log_msg((LOG_ERROR_LEVEL,
			   "Unrecognized variable modifier flag: %s", var->flagstr));
	  return(-1);
	  /*NOTREACHED*/
	}
  }

  return(0);
}

/*
 * A variable name is:
 *    [namespace::] variable-name
 */
Var_ref *
var_parse_ref(char *str)
{
  char *p, *ref;
  Var_ref *vr;

  vr = ALLOC(Var_ref);

  p = ref = strdup(str);
  while (*p != ':' && *p != '\0')
	p++;

  vr->ns = NULL;
  if (*p != '\0') {
	/* We are looking at the start of a namespace separator. */
	if (!strneq(p, VAR_NS_SEP_STR, strlen(VAR_NS_SEP_STR)))
	  return(NULL);

	vr->ns = ref;
	*p = '\0';
	p += strlen(VAR_NS_SEP_STR);
  }

  if (vr->ns != NULL) {
	if (*vr->ns == '\0')
	  return(NULL);
  }
  else {
#ifndef DEFAULT_NAMESPACE
	return(NULL);
#else
	vr->ns = DEFAULT_NAMESPACE;
	if (*p == '\0')
	  vr->varname = ref;
	else
	  vr->varname = p;
#endif
  }

  if (!var_ns_is_valid_namespace_name(vr->ns))
	return(NULL);

  if (!var_ns_is_valid_varname(vr->varname, NULL))
	return(NULL);

  return(vr);
}

/*
 * Parse a full variable reference from STR.
 * STR is expected to be the beginning of a variable reference.
 * Return the variable (or NULL on error), including namespace and flags.
 * Also set ENDP (if non-NULL) to the first character following the variable
 * reference (i.e., the character following the reference's closing '}'
 * character), which may be NUL.
 */
Var *
var_parse_name(char *str, char **endp)
{
  int sep_length;
  char *p;
  Ds *flagstr, *name;
  Var *var;

  /* XXX this should not be hardwired */
  if (*str != '$' || *(str + 1) != '{')
	return(NULL);

  p = str + 2;

  sep_length = strlen(VAR_NS_SEP_STR);

  var = ALLOC(Var);
  var->varname = NULL;
  var->ns = NULL;
  var->name = NULL;
  var->flagstr = NULL;
  var->flags = VAR_DEFAULT_FLAG;
  var->altval = NULL;
  name = ds_init(NULL);

  /*
   * Extract an identifier.
   * We're either looking at a namespace or a variable name.
   * XXX This assumes that both the VAR_NS_SEP_STR and the modifier flag
   * separator begin with a colon...
   */
  while (*p != ':' && *p != '}' && *p != '\0') {
	ds_appendc(name, *p);
	p++;
  }
  ds_appendc(name, '\0');

  if (*p == '\0')
	goto error;

  if (strneq(p, VAR_NS_SEP_STR, sep_length)) {
	/* We found a (possibly empty) namespace identifier. */
	var->ns = strdup(ds_buf(name));
	if (!var_ns_is_valid_namespace_name(var->ns))
	  goto error;
	p += sep_length;

	name = ds_init(NULL);

	/* Now get the variable name. */
	while (*p != ':' && *p != '}' && *p != '\0') {
	  ds_appendc(name, *p);
	  p++;
	}
	ds_appendc(name, '\0');
	if (*p == '\0')
	  goto error;
  }

  var->name = strdup(ds_buf(name));
  if (!var_ns_is_valid_varname(var->name, NULL))
	goto error;

  /* We're looking at either the modifier separator or the closing brace. */
  if (*p == ':') {
	p++;
	if (*p == '}' || *p == '\0')	/* No flag(s)? */
	  goto error;

	flagstr = ds_init(NULL);
	while (*p != '\0' && *p != '}') {
	  if (*p == '\\' && *(p + 1) == '}')
		p++;
	  ds_appendc(flagstr, *p++);
	}
	ds_appendc(flagstr, '\0');

	if (*p == '}') {
	  var->flagstr = ds_buf(flagstr);
	  if (var_parse_flags(var) == -1)
		goto error;
	}
  }

  if (*p != '}') {
  error:
	ds_free(name);
	return(NULL);
  }
  p++;

  if (endp != NULL)
	*endp = p;

  if (var->ns != NULL)
	var->varname = ds_xprintf("${%s%s%s}", var->ns, VAR_NS_SEP_STR, var->name);
  else
	var->varname = ds_xprintf("${%s}", var->name);

  return(var);
}

/*
 * Parse a variable reference starting at STR, setting ENDP to point to the
 * character that follows the reference and VARP to parsed variable, and
 * resolve the reference as a string value by calling RESOLVE with RESOLVE_ARG.
 */
Ds *
var_value(char *str, Var **varp, char **endp, Var_resolve resolve,
		  void *resolve_arg)
{
  char *value;
  Ds *ds;
  Var *var;

  if ((var = var_parse_name(str, endp)) == NULL)
	return(NULL);

  /* Lookup and interpolate the variable's value */
  if ((ds = resolve(var, resolve_arg, NULL, NULL)) == NULL)
	return(NULL);
  value = ds_buf(ds);

  if (varp != NULL)
	*varp = var;

  return(ds);
}

/*
 * Expand all variable references in a string, returning a new string.
 * If an error occurs, NULL is returned.
 * The callback function RESOLVE is invoked to determine the string value
 * of each variable reference.  It is called with the variable
 * and RESOLVE_ARG; it
 * returns the value of the variable or NULL if an error occurs.
 */
Ds *
var_expand(char *str, Var_resolve resolve, void *resolve_arg)
{
  char *endp, *p;
  Ds *ds;

  if (str == NULL)
	return(NULL);

  if (*str == '\0')
	return(ds_set(NULL, ""));

  p = str;
  ds = ds_init(NULL);

  while (*p != '\0') {
	if (*p == '\\') {
#ifdef NOTDEF
	  p++;
	  if (*p == '\0')
		return(NULL);
	  ds_concatc(ds, (int) *p);
	  p++;
#else
	  int cc, i;

	  /* Handle C-style character constants. */
	  p++;
	  if (*p == '\0')	{
		/* Ends with an unescaped backslash? */
		return(NULL);
	  }

	  if (is_octdigit((int) *p)) {
		cc = 0;
		for (i = 0; i < 3; i++) {
		  if (!is_octdigit((int) *p))
			break;
		  cc = cc * 8 + octdigit_val((int) *p);
		  p++;
		}
		ds_concatc(ds, cc);
	  }
	  else {
		/* Harbison & Steele 2.7.6 */
		switch ((int) *p) {
		case 'a':
		  cc = '\a'; break;
		case 'b':
		  cc = '\b'; break;
		case 'f':
		  cc = '\f'; break;
		case 'n':
		  cc = '\n'; break;
		case 'r':
		  cc = '\r'; break;
		case 't':
		  cc = '\t'; break;
		case 'v':
		  cc = '\v'; break;
		default:
		  cc = *p; break;
		}

		ds_concatc(ds, cc);
		p++;
	  }
#endif
	}
	else if (*p == '$' && *(p + 1) == '{') {
	  Ds *v;

	  if ((v = var_value(p, NULL, &endp, resolve, resolve_arg)) == NULL) {
		ds_free(ds);
		log_msg((LOG_ERROR_LEVEL, "Illegal variable reference: %s", str));
		return(NULL);
	  }
	  ds_concat(ds, ds_buf(v));		/* XXX more efficient way? */
	  p = endp;
	}
	else if (*p == '$' && (isalpha((int) *(p + 1)) || *(p + 1) == '_')) {
	  char *q;
	  Ds *v, avar;
	  size_t var_ns_sep_str_len;

	  /* A simple variable reference; e.g., $foo */
	  ds_init(&avar);
	  ds_appendc(&avar, '$');
	  ds_appendc(&avar, '{');
	  q = p + 1;
	  if (!isalpha((int) *q) && *q != '_')
		return(NULL);

	  var_ns_sep_str_len = strlen(VAR_NS_SEP_STR);
	  while (*q != '\0') {
		if (!var_ns_sep_str_len && *q == '#')
		  ds_appendc(&avar, *q++);
		if (isalnum((int) *q) || *q == '_')
		  ds_appendc(&avar, *q++);
		else if (var_ns_sep_str_len
				 && strneq(p, VAR_NS_SEP_STR, var_ns_sep_str_len)) {
		  ds_append(&avar, VAR_NS_SEP_STR);
		  q += var_ns_sep_str_len;
		  /* Don't allow this to appear again */
		  var_ns_sep_str_len = 0;
		}
		else
		  break;
	  }
	  if (q == str + 1)
		return(NULL);
	  ds_appendc(&avar, '}');
	  ds_appendc(&avar, '\0');

	  if ((v = var_value(ds_buf(&avar), NULL, &endp, resolve, resolve_arg))
		  == NULL) {
		ds_free(&avar);
		log_msg((LOG_ERROR_LEVEL, "Illegal variable reference: %s", str));
		return(NULL);
	  }
	  ds_concat(ds, ds_buf(v));		/* XXX more efficient way? */
	  p = q;
	}
	else {
	  ds_concatc(ds, (int) *p);
	  p++;
	}
  }

  return(ds);
}

static Var_ns *
lookup(Var_ns *root, char *ns)
{
  char *n;
  Var_ns *v;

  if (ns == NULL) {
#ifdef DEFAULT_NAMESPACE
	n = DEFAULT_NAMESPACE;
#else
	log_msg((LOG_ERROR_LEVEL,
			 "Invalid variable reference: namespace is required"));
	return(NULL);
#endif
  }
  else
	n = ns;

  if (!var_ns_is_valid_namespace_name(n))
	return(NULL);

  for (v = root; v != NULL; v = v->next) {
	if (streq(n, v->ns))
	  return(v);
  }

  return(NULL);
}

Var_ns *
var_ns_lookup(Var_ns *root, char *ns)
{

  return(lookup(root, ns));
}

/*
 * Return 1 iff NS_NAME exists and is marked read-only, 0 otherwise.
 */
int
var_ns_is_readonly(Var_ns *root, char *ns_name)
{
  Var_ns *ns;

  if ((ns = var_ns_lookup(root, ns_name)) == NULL)
	return(0);

  return((ns->flags & VAR_NS_READONLY) != 0);
}

int
var_ns_is_reserved(Var_ns *root, char *ns_name)
{
  Var_ns *ns;

  if ((ns = var_ns_lookup(root, ns_name)) == NULL)
	return(0);

  return((ns->flags & VAR_NS_RESERVED) != 0);
}

unsigned int
var_ns_set_flags(Var_ns *root, char *ns_name, unsigned int flags)
{
  unsigned int oflags;
  Var_ns *ns;

  if ((ns = var_ns_lookup(root, ns_name)) == NULL)
	return(-1);

  oflags = ns->flags;
  ns->flags = flags;

  return(oflags);
}

/*
 * Create, initialize, and return a new namespace, NS.
 * If KWV isn't NULL, it contains initial variables and their values.
 * XXX This should probably take a flags argument.
 */
Var_ns *
var_ns_create(char *ns, Kwv *kwv)
{
  char *n;
  Var_ns *v;
  extern int acs_is_readonly_namespace(char *ns);

  if (ns == NULL) {
#ifdef DEFAULT_NAMESPACE
	n = DEFAULT_NAMESPACE;
#else
	log_msg((LOG_ERROR_LEVEL,
			 "Invalid variable reference: namespace is required"));
	return(NULL);
#endif
  }
  else
	n = ns;

  if (!var_ns_is_valid_namespace_name(n))
	return(NULL);

  v = ALLOC(Var_ns);

  v->flags = VAR_NS_DEFAULT;
  if (acs_is_readonly_namespace(ns))
	v->flags |= VAR_NS_READONLY;

  v->ns = strdup(n);
  if (kwv != NULL)
	v->kwv = kwv;
  else
	v->kwv = kwv_init(10);
  v->prev = NULL;
  v->next = NULL;

  return(v);
}

/*
 * Add a namespace, V, to the list of namespaces pointed to by ROOT.
 */
Var_ns *
var_ns_add(Var_ns **root, Var_ns *v)
{

  if (*root != NULL)
	(*root)->prev = v;
  v->prev = NULL;
  v->next = *root;
  *root = v;

  return(v);
}

/*
 * Create and initialize a new namespace, NAME, and add it to the
 * list of namespaces, ROOT.  If KWV is non-NULL, it contains initial
 * variables for the namespace.  Return the new namespace.
 * If the namespace already exists, reset its contents to KWV and return it.
 * If the namespace cannot be created, return NULL,
 */
Var_ns *
var_ns_new(Var_ns **root, char *name, Kwv *kwv)
{
  Var_ns *v;

  if (*root != NULL && (v = lookup(*root, name)) != NULL) {
	if (v->kwv != NULL)
	  kwv_free(v->kwv);
	v->kwv = kwv;

	return(v);
  }

  if ((v = var_ns_create(name, kwv)) != NULL)
	v = var_ns_add(root, v);

  return(v);
}

/*
 * Delete the namespace represented by V from the list of namespaces pointed
 * to by ROOT.
 */
static Var_ns *
do_delete(Var_ns **root, Var_ns *v)
{

  /* kwv_delete(v->kwv); */
  if (v->prev != NULL)
	(v->prev)->next = v->next;
  else
	*root = v->next;

  if (v->next != NULL)
	(v->next)->prev = v->prev;

  return(v->next);
}

/*
 * If namespace NAME exists, remove it from the list pointed to by ROOT.
 */
int
var_ns_delete(Var_ns **root, char *name)
{
  Var_ns *v;

  if ((v = lookup(*root, name)) == NULL)
	return(-1);

  do_delete(root, v);

  return(0);
}

/*
 * If namespace NAME exists, replace its contents with KWV; otherwise,
 * create the new namespace.
 */
int
var_ns_replace(Var_ns **root, char *name, Kwv *kwv)
{
  Var_ns *v;

  if ((v = lookup(*root, name)) == NULL)
	return(var_ns_new(root, name, kwv) == NULL ? -1 : 0);

  if (v->kwv != NULL && v->kwv != kwv)
	kwv_free(v->kwv);

  v->kwv = kwv;

  return(0);
}

/*
 * Delete every namespace in the list pointed by ROOT that is marked as
 * temporary.
 */
int
var_ns_delete_temporaries(Var_ns **root)
{
  Var_ns *v;

  if (*root == NULL)
	return(0);

  v = *root;
  while (v != NULL) {
	if (v->flags & VAR_NS_TEMPORARY)
	  v = do_delete(root, v);
	else
	  v = v->next;
  }

  return(0);
}

/*
 * Look for namespace NAME; if found, return its variables.
 */
Kwv *
var_ns_lookup_kwv(Var_ns *root, char *name)
{
  Var_ns *v;

  if ((v = lookup(root, name)) == NULL)
	return(NULL);

  return(v->kwv);
}

/*
 * Convert a variable reference to text.
 */
char *
var_ns_varname(char *ns, char *name)
{
  char *p;

  p = ds_xprintf("${%s::%s}", ns, name);

  return(p);
}

/*
 * Look for namespace NAME; if found, lookup the variable named KEY.
 */
Kwv_pair *
var_ns_get_pair(Var_ns *root, char *name, char *key)
{
  Var_ns *v;

  if ((v = lookup(root, name)) == NULL)
	return(NULL);

  return(kwv_lookup(v->kwv, key));
}

/*
 * Look for namespace NAME; if found, lookup the first occurrence of KEY
 * and return its value.
 */
char *
var_ns_get_value(Var_ns *root, char *name, char *key)
{
  Var_ns *v;


  if ((v = lookup(root, name)) == NULL)
	return(NULL);

  return(kwv_lookup_value(v->kwv, key));
}

/*
 * Look for namespace NAME; if found, add/replace the value of any existing
 * variable KEY with VAL.
 */
Kwv *
var_ns_add_key(Var_ns *root, char *name, char *key, char *val)
{
  Kwv *kwv;

  if ((kwv = var_ns_lookup_kwv(root, name)) == NULL)
	return(NULL);

  /* We want to replace an existing value, if present. */
  return(kwv_replace(kwv, key, val));
}

/*
 * Look for namespace NAME; if found, add/replace the value of any existing
 * variable KEY with VAL.
 */
Kwv *
var_ns_add_var(Var_ns *root, char *name, char *key, char *val, void *xval)
{
  Kwv *kwv;
  Kwv_pair *pair;

  if ((kwv = var_ns_lookup_kwv(root, name)) == NULL)
	return(NULL);

  /* We want to replace an existing value, if present. */
  pair = kwv_new_pair(key, val, xval);
  return(kwv_replace_pair(kwv, pair));
}

/*
 * Make a copy of all of the namespaces in ROOT, except those in the
 * (possibly empty) list EXCLUDE.
 */
Var_ns *
var_ns_copy_exclude(Var_ns *root, Dsvec *exclude)
{
  Var_ns *new_root, *v, *vn, *vn_prev;

  if (root == NULL)
	return(NULL);

  new_root = NULL;
  vn_prev = NULL;

  for (v = root; v != NULL; v = v->next) {
	int ex, i;
	char *ns;

	ex = 0;
	for (i = 0; i < dsvec_len(exclude); i++) {
	  ns = (char *) dsvec_ptr_index(exclude, i);
	  if (streq(ns, v->ns)) {
		ex = 1;
		break;
	  }
	}

	if (ex)
	  continue;

	vn = ALLOC(Var_ns);
	vn->ns = strdup(v->ns);
	vn->kwv = kwv_copy(v->kwv);
	vn->flags = v->flags;
	if (new_root == NULL) {
	  new_root = vn;
	  vn->prev = NULL;
	}
	else {
	  vn->prev = vn_prev;
	  vn->prev->next = vn;
	}
	vn->next = NULL;
	vn_prev = vn;
  }

  return(new_root);
}

/*
 * Make a copy of all of the namespaces in ROOT.
 */
Var_ns *
var_ns_copy(Var_ns *root)
{

  return(var_ns_copy_exclude(root, NULL));
}

Var_ns *
var_ns_from_env(char *ns_name)
{
  int i;
  Var_ns *ns;
  extern char **environ;

  ns = var_ns_create(ns_name, NULL);
  if (ns == NULL)
	return(NULL);
  
  for (i = 0; environ[i] != NULL; i++) {
	char *name, *value;

	if (env_parse(environ[i], &name, &value) != NULL
		&& var_ns_is_valid_varname(name, NULL))
	  kwv_replace(ns->kwv, name, value);
  }

  ns->flags = VAR_NS_READONLY | VAR_NS_RESERVED;

  return(ns);
}

/*
 * Create a list, one per line, of namespace names pointed to by ROOT.
 */
char *
var_ns_names(Var_ns *root)
{
  Ds ds;
  Var_ns *v;

  if (root == NULL)
	return("");

  ds_init(&ds);
  for (v = root; v != NULL; v = v->next) {
	ds_asprintf(&ds, "%s%s", (v == root) ? "" : ",", v->ns);
  }

  return(ds_buf(&ds));
}

/*
 * Look for namespace NAME in the list pointed to by ROOT and return
 * a string containing all of its variables and their values.
 */
char *
var_ns_buf(Var_ns *root, char *name)
{
  char *str;
  Kwv *kwv;

  if ((kwv = var_ns_lookup_kwv(root, name)) == NULL)
	return("");

  str = kwv_buf(kwv, '=', '"');
  return(str);
}
