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

/*
 * Document transformation by redaction, selection, and substitution of
 * content.  Ordinary content and special directives can be combined in the
 * input document(s).
 *
 * A directive can insert new content, process and replace content, or
 * perform computations.
 *
 * Directives:
 * 1. begin/end
 *    Include or exclude verbatim content
 * 2. debug
 *    Emit variable values
 * 3. expand
 *    Include or exclude content with variable substitutions
 * 4. filter/end, filterv/end
 *
 * 5. id
 *    Emit an identification string
 * 6. insert, insertv
 *
 * 7. set
 *    Set or reset zero or more variables
 */

#ifndef lint
static const char copyright[] =
"Copyright (c) 2003-2012\n\
Distributed Systems Software.  All rights reserved.";
static const char revid[] =
  "$Id: transform.c 2594 2012-10-19 17:28:49Z brachman $";
#endif

#include "dacs.h"
#include "dacs_api.h"

#include <sys/utsname.h>

#ifndef DACS_TRANSFORM_ACLS
#define DACS_TRANSFORM_ACLS	"[transform-acls]dacs-fs:"/**/DACS_HOME/**/"/dacs_transform/acls"
#endif

#ifndef DACS_TRANSFORM_DOCS
#define DACS_TRANSFORM_DOCS	DACS_HOME/**/"/dacs_transform/docs"
#endif

#define DEFAULT_SGML_DIRECTIVE_PREFIX	"<!--DACS "
#define DEFAULT_SGML_DIRECTIVE_SUFFIX	"-->"

#ifndef DEFAULT_HTML_ANNOTATION
#define DEFAULT_HTML_ANNOTATION		"<b>*** Content elided ***</b>"
#endif

static MAYBE_UNUSED const char *log_module_name = "dacs_transform";

#ifndef PROG

typedef struct Directive_tab {
  char *name;
  Transform_directive directive;
} Directive_tab;

static Directive_tab directive_tab[] = {
  { "begin",   D_BEGIN },
  { "comment", D_COMMENT },
  { "debug",   D_DEBUG },
  { "end",     D_END },
  { "eval",    D_EVAL },
  { "expand",  D_EXPAND },
  { "filter",  D_FILTER },
  { "filterv", D_FILTERV },
  { "id",      D_ID },
  { "insert",  D_INSERT },
  { "insertv", D_INSERTV },
  { "set",     D_SET },
  { NULL,      D_NONE }
};

static Transform_directive
get_directive(Kwv *kwv, char **value, char **errmsg)
{
  int i;
  char *v;
  Transform_directive d;

  kwv_set_mode(kwv, "+i-d");
  d = D_NONE;

  for (i = 0; directive_tab[i].name != NULL; i++) {
	if ((v = kwv_lookup_value(kwv, directive_tab[i].name)) != NULL) {
	  if (d != D_NONE) {
		if (errmsg != NULL)
		  *errmsg = "Only one directive is allowed";
		return(D_ERROR);
	  }
	  d = directive_tab[i].directive;
	  *value = v;
	}
  }

  return(d);
}

static Acs_expr_result
eval(char *expr, Kwv *kwv_attrs, Var_ns **namespaces, char **result_str)
{
  Acs_expr_result st;
  Acs_expr_ns_arg *ns_args;

  var_ns_replace(namespaces, "Attr", kwv_attrs);
  ns_args = var_ns_to_acs_ns(*namespaces);

  st = acs_expr_string(expr, ns_args, result_str);

  if (st == ACS_EXPR_TRUE && result_str != NULL && *result_str != NULL)
	log_msg((LOG_TRACE_LEVEL, "Eval result: \"%s\"", *result_str));

  return(st);
}

/*
 * Attribute values quoted by backticks are evaluated as an expression,
 * but other values are copied verbatim.
 */
static Kwv *
attrs(Kwv *kwv, Var_ns **namespaces)
{
  char *newval, *p;
  Kwv *kwv_attrs;
  Kwv_iter *iter;
  Kwv_pair *pair;

  kwv_attrs = kwv_init(4);
  kwv_set_mode(kwv, "+i-d");

  iter = kwv_iter_begin(kwv, NULL);
  for (pair = kwv_iter_first(iter); pair != NULL; pair = kwv_iter_next(iter)) {
	if (pair->val != NULL) {
	  p = strdequote(pair->val);
	  if (pair->val[0] == '`') {
		if (eval(p, kwv, namespaces, &newval) < 0)
		  return(NULL);
	  }
	  else
		newval = p;

	  kwv_add(kwv_attrs, pair->name, newval);
	}
  }
  kwv_iter_end(iter);

  return(kwv_attrs);
}

static int
is_directive(Transform_config *tc, char *line, char **startp, char **endp)
{
  char *e, *s, *suffix, *suffix_crlf;
  size_t slen;

  if ((s = strprefix(line, tc->directive_prefix)) != NULL) {
	/* Remember that lines may end with a \n or a \r\n. */
	slen = strlen(s);
	suffix = ds_xprintf("%s\n", tc->directive_suffix);
	suffix_crlf = ds_xprintf("%s\r\n", tc->directive_suffix);

	if ((e = strsuffix(s, slen, suffix)) == NULL
		&& (e = strsuffix(s, slen, suffix_crlf)) == NULL)
	  return(-1);
	*startp = s;
	*endp = e;
	return(1);
  }

  return(0);
}

static int
is_regex_directive(char *line, regex_t *regex, char **startp, char **endp,
				   char **errmsg)
{
  int rc, st;
  regmatch_t m[4];

  if ((st = regexec(regex, line, 4, m, 0)) == 0) {
	if (m[0].rm_so == -1 || m[1].rm_so == -1 || m[2].rm_so == -1
		|| m[3].rm_so != -1)
	  return(-1);

	*startp = line + m[1].rm_eo;
	*endp = line + m[2].rm_so;
	return(1);
  }
  else if (st != REG_NOMATCH) {
	char errbuf[100];

	if (errmsg != NULL) {
	  errbuf[0] = '\0';
	  regerror(st, regex, errbuf, sizeof(errbuf));
	  *errmsg = ds_xprintf("bad regular expression: \"%s\": %s",
						   regex, errbuf);
	}
	rc = -1;
  }
  else
	rc = 0;

  return(rc);
}

/*
 * Check if a region should be processed or discarded.
 * Return 1 if the former, 0 if the latter, or -1 if an error occurs.
 * If the region has a "cond" attribute, its value is an expression that
 * must evaluate to True for the region to be processed.
 * If the name of the region is "*", then no rule needs to be consulted,
 * otherwise a rule must be checked.
 * If both a "cond" attribute and a rule are specified, then both must
 * be True for the region's content to be included.
 */
static int
region_test(Transform_config *tc, char *region, Kwv *kwv_attrs, Kwv *kwv_conf,
			Kwv *kwv_dacs, char *op, int invert, char *acls,
			char *object_name, char *idents, char **errmsg)
{
  int nargs, st;
  char **constraints, *expr, *object;
  Ds args;
  Kwv_iter *iter;
  Kwv_pair *pair;

  if ((expr = kwv_lookup_value(kwv_attrs, "cond")) != NULL) {
	char *result;
	Acs_expr_result rc;

	tc->env->namespaces = tc->stackp->var_context;
	rc = acs_expr_string_env(expr, tc->env, &result);
	if (acs_expr_error_occurred(rc)) {
	  if (errmsg != NULL)
		*errmsg = ds_xprintf("Line %d: expansion error: \"%s\"",
							 tc->linenum, expr);
	  return(-1);
	}
	st = (rc == ACS_EXPR_TRUE);
  }
  else
	st = ACS_EXPR_TRUE;

  if (st == ACS_EXPR_TRUE && !streq(region, "*")) {
	ds_init(&args);
	nargs = 0;
	iter = kwv_iter_begin(kwv_attrs, NULL);
	for (pair = kwv_iter_first(iter); pair != NULL;
		 pair = kwv_iter_next(iter)) {
	  if (strcaseeq(pair->name, op))
		ds_asprintf(&args, "%sregion=%s",
					(nargs == 0) ? "" : "&",
					invert ? region + 1 : region);
	  else
		ds_asprintf(&args, "%s%s=%s",
					(nargs == 0) ? "" : "&", pair->name, pair->val);
	  nargs++;
	}
	kwv_iter_end(iter);

	object = ds_xprintf("%s?%s", object_name, ds_buf(&args));
	st = check_access(acls, NULL, idents, object,
					  kwv_conf, kwv_dacs, NULL, &constraints);
  }

  return(st);
}

Transform_config *
transform_init(Transform_config *otc)
{
  char *p;
  Transform_config *tc;

  if (otc == NULL)
	tc = ALLOC(Transform_config);
  else
	tc = otc;

  tc->ds_in = ds_init(NULL);
  tc->ds_out = ds_init(NULL);

  tc->linenum = 0;
  tc->stack_depth = 0;
  tc->stackp = NULL;
  tc->max_stack_depth = TRANSFORM_MAX_STACK_DEPTH;

  tc->acls = DACS_TRANSFORM_ACLS;
  tc->docs = DACS_TRANSFORM_DOCS;
  tc->directive_prefix = DEFAULT_SGML_DIRECTIVE_PREFIX;
  tc->directive_suffix = DEFAULT_SGML_DIRECTIVE_SUFFIX;

  tc->insert_dir = NULL;
  tc->annotation = NULL;
  tc->regex_prefix = NULL;
  tc->regex_suffix = NULL;

  if (dacs_conf != NULL) {
	p = var_ns_get_value(dacs_conf->conf_var_ns, "Conf", "transform_acls");
	if (p != NULL)
	  tc->acls = p;

	p = var_ns_get_value(dacs_conf->conf_var_ns, "Conf", "transform_docs");
	if (p != NULL)
	  tc->docs = p;

	p = var_ns_get_value(dacs_conf->conf_var_ns, "Conf", "transform_prefix");
	if (p != NULL)
	  tc->directive_prefix = p;

	p = var_ns_get_value(dacs_conf->conf_var_ns, "Conf", "transform_suffix");
	if (p != NULL)
	  tc->directive_suffix = p;

	p = var_ns_get_value(dacs_conf->conf_var_ns, "Conf", "transform_rprefix");
	if (p != NULL)
	  tc->regex_prefix = p;

	p = var_ns_get_value(dacs_conf->conf_var_ns, "Conf", "transform_rsuffix");
	if (p != NULL)
	  tc->regex_suffix = p;

	tc->annotation = var_ns_get_value(dacs_conf->conf_var_ns, "Conf",
									  "transform_annotation");
  }
  tc->global_attrs = kwv_init(4);
  tc->env = acs_new_env(NULL);

  return(tc);
}

static Transform_config *
transform_reconfig(Transform_config *otc, Ds *in, Ds *out)
{
  Transform_config *tc;

  tc = ALLOC(Transform_config);

  tc->ds_in = in;
  tc->ds_out = out;

  tc->linenum = 0;
  tc->stack_depth = otc->stack_depth;
  tc->max_stack_depth = otc->max_stack_depth;
  tc->stackp = otc->stackp;

  tc->acls = otc->acls;
  tc->docs = otc->docs;
  tc->insert_dir = otc->insert_dir;
  tc->directive_prefix = otc->directive_prefix;
  tc->directive_suffix = otc->directive_suffix;

  tc->annotation = otc->annotation;
  tc->regex_prefix = otc->regex_prefix;
  tc->regex_suffix = otc->regex_suffix;

  tc->global_attrs = kwv_copy(otc->global_attrs);

  tc->env = otc->env;

  return(tc);
}

static Transform_stack *
push_stack(Transform_config *tc, Transform_directive directive, char *region,
		   Kwv *attrs, int skip, int expand, char *expr)
{
  Transform_stack *stack_el;

  stack_el = ALLOC(Transform_stack);
  stack_el->directive = directive;
  stack_el->region = region;
  stack_el->skipping = skip;
  stack_el->expanding = expand;
  stack_el->expr = expr;
  stack_el->diverted = ds_init(NULL);

  /*
   * Merge any global Attr variables into the new context.
   * Get an initial variable context for the new context; if this is not
   * happening at the top level, merge the upper levels' Attr variables.
   */
  kwv_merge(attrs, tc->global_attrs, KWV_NO_DUPS);
  if (tc->stackp == NULL) {
	stack_el->saved_attrs = NULL;
	stack_el->var_context = var_ns_copy(tc->env->namespaces);
  }
  else {
	Kwv *kwv;

	kwv = var_ns_lookup_kwv(tc->stackp->var_context, "Attr");
	stack_el->saved_attrs = kwv;
	stack_el->var_context = var_ns_copy(tc->stackp->var_context);
	if (kwv != NULL)
	  kwv_merge(attrs, kwv, KWV_NO_DUPS);
  }
  var_ns_replace(&stack_el->var_context, "Attr", attrs);

  stack_el->prev = tc->stackp;
  tc->stackp = stack_el;
  tc->stack_depth++;

  return(stack_el);
}

static int
pop_stack(Transform_config *tc)
{
  Transform_stack *stack_el;

  if (tc->stackp == NULL || tc->stack_depth == 0)
	return(-1);

  stack_el = tc->stackp;
  tc->stackp = stack_el->prev;
  tc->stack_depth--;
  free(stack_el);

  return(0);
}

/*
 * A quick and dirty API for processing input from FP using
 * variables KWV in the DACS namespace, but without any rules
 * (therefore, no directive must require rule evaluation).
 */
Ds *
transform_simple_fp(FILE *fp, Kwv *kwv, char **errmsg)
{
  Transform_config *tc;

  tc = transform_init(NULL);

  var_ns_new(&tc->env->namespaces, "DACS", kwv);
  dsio_set(tc->ds_in, fp, NULL, 0, 0);
  if (transform(tc, "", NULL, errmsg) == -1)
	return(NULL);

  return(tc->ds_out);
}

Ds *
transform_simple_file(char *path, Kwv *kwv, char **errmsg)
{
  Ds *ds_out;
  FILE *fp;

  if ((fp = fopen(path, "r")) == NULL) {
	if (errmsg != NULL)
	  *errmsg = ds_xprintf("Cannot read \"%s\": %s", path, strerror(errno));
	return(NULL);
  }

  ds_out = transform_simple_fp(fp, kwv, errmsg);
  fclose(fp);

  return(ds_out);
}

Ds *
transform_simple_str(char *input, Kwv *kwv, char **errmsg)
{
  Transform_config *tc;

  tc = transform_init(NULL);

  var_ns_new(&tc->env->namespaces, "DACS", kwv);
  dsio_set(tc->ds_in, NULL, input, 0, 0);
  if (transform(tc, "", NULL, errmsg) == -1)
	return(NULL);

  return(tc->ds_out);
}

static int
test_transform_simple(FILE *fp_out, char *filename)
{
  char *errmsg;
  Ds *ds_out;
  FILE *fp;
  Kwv *kwv;

  kwv = kwv_init(8);
  kwv_add(kwv, "dog1", "Auggie");
  kwv_add(kwv, "dog2", "Harley");
  kwv_add(kwv, "dog3", "Bandito");
  errmsg = NULL;
  ds_out = transform_simple_file(filename, kwv, &errmsg);
  if (ds_out == NULL) {
	if (errmsg != NULL)
	  fprintf(stderr, "%s\n", errmsg);
	return(-1);
  }

  if ((fp = fp_out) == NULL)
	fp = stdout;
  if (ds_len(ds_out) > 0)
	fprintf(fp, "%s", ds_buf(ds_out));

  return(0);
}

/*
 * Transform input from OBJECT_NAME, customized for IDENTS (a username,
 * or unauthenticated if NULL).
 * Return 0 if ok, -1 if an error occurs.
 */
int
transform(Transform_config *tc, char *object_name, char *idents, char **errmsg)
{
  int dcount, elided, expand, granted, invert, recurse, st;
  char *buf, *e, *regex_str, *region, *line, *s, *suffix, *username;
  char *opname, *expr_attr, *filename_attr, *uri_attr, *p;
  char *result_str;
  regex_t *regex;
  Acs_expr_result rc;
  Transform_directive directive;
  Http_method method;
  Kwv *kwv_attrs, *kwv_conf, *kwv_dacs;
  Transform_stack *stack_el, *current_stackp;
  static Kwv_conf directive_conf = {
	"=", "'\"`", NULL, KWV_CONF_KEEPQ, NULL, 4, NULL, NULL
  };

  if (idents == NULL)
	username = "unauth";
  else
	username = idents;
  log_msg((LOG_TRACE_LEVEL, "User is %s", username));

  if ((kwv_conf = var_ns_lookup_kwv(tc->env->namespaces, "Conf")) == NULL)
	kwv_conf = kwv_init(4);
  if ((kwv_dacs = var_ns_lookup_kwv(tc->env->namespaces, "DACS")) == NULL)
	kwv_dacs = kwv_init(4);

  current_stackp = tc->stackp;

  /*
   * XXX could push a pseudo element here to wrap document with a default.
   * If so, alter final tag balance test
   */

  s = NULL;
  region = NULL;
  e = NULL;
  method = HTTP_UNKNOWN_METHOD;
  expr_attr = NULL;

  elided = 0;

  if (tc->regex_prefix != NULL || tc->regex_suffix != NULL) {
	regex_str = ds_xprintf("^\\(%s\\).*\\(%s\\)\n$",
						   (tc->regex_prefix != NULL)
						   ? tc->regex_prefix : tc->directive_prefix,
						   (tc->regex_suffix != NULL)
						   ? tc->regex_suffix : tc->directive_suffix);
	regex = ALLOC(regex_t);
	if (regcomp(regex, regex_str, 0) != 0) {
	  if (errmsg != NULL)
		*errmsg = "Invalid regular expression";
	  goto fail;
	}
  }
  else {
	regex = NULL;
	regex_str = NULL;
  }

  /*
   * Read and process each input line.
   */
  tc->linenum = 0;
  while ((line = dsio_agets(tc->ds_in)) != NULL) {
	tc->linenum++;
	if (regex_str == NULL) {
	  if ((st = is_directive(tc, line, &s, &e)) == -1) {
		if (errmsg != NULL)
		  *errmsg = ds_xprintf("Line %d: invalid DACS tag", tc->linenum);
		goto fail;
	  }
	}
	else if ((st = is_regex_directive(line, regex, &s, &e, errmsg)) == -1)
	  goto fail;

    if (st == 1) {
	  Kwv *kwv;

	  /* This is a directive. */
	  suffix = strdup(e);
	  *e = '\0';

	  kwv = kwv_init(4);
	  kwv_set_mode(kwv, "-d");
	  if (kwv_make_sep(kwv, s, &directive_conf) == NULL) {
		if (errmsg != NULL)
		  *errmsg
			= ds_xprintf("Line %d: invalid attribute specification: \"%s\"",
						 tc->linenum, s);
		goto fail;
	  }

	  if ((kwv_attrs = attrs(kwv, &tc->env->namespaces)) == NULL) {
		if (errmsg != NULL)
		  *errmsg = ds_xprintf("Line %d: expression evaluation error",
							   tc->linenum);
		goto fail;
	  }

	  directive = get_directive(kwv_attrs, &region, errmsg);
	  if (*region == '!')
		invert = 1;
	  else
		invert = 0;

	  if ((p = kwv_lookup_value(kwv_attrs, "recurse")) != NULL
		  && !strcaseeq(p, "yes"))
		recurse = 0;
	  else
		recurse = 1;

	  if (directive == D_EXPAND)
		expand = 1;
	  else
		expand = 0;

	  switch (directive) {
	  case D_BEGIN:
		/*
		 * begin/end surround content that, only if selected, is variable
		 * expanded (if enabled) and recursively processed (if enabled).
		 */
		if (tc->stack_depth == tc->max_stack_depth) {
		  if (errmsg != NULL)
			*errmsg = ds_xprintf("Line %d: DACS tag stack overflow",
								 tc->linenum);
		  goto fail;
		}

		stack_el = push_stack(tc, directive, region, kwv_attrs, 1,
							  expand, NULL);

		log_msg((LOG_TRACE_LEVEL, "begin: check if \"%s\" can access \"%s\"",
				 username, region));

		st = region_test(tc, region, kwv_attrs, kwv_conf, kwv_dacs, "begin",
						 invert, tc->acls, object_name, idents, errmsg);

		if (invert == 0 && st == 1) {
		  tc->stackp->skipping = 0;
		  log_msg((LOG_TRACE_LEVEL, "Access granted"));
		}
		else if (invert == 0 && st == 0)
		  log_msg((LOG_TRACE_LEVEL, "Access denied"));
		else if (invert == 1 && st == 0) {
		  tc->stackp->skipping = 0;
		  log_msg((LOG_TRACE_LEVEL, "Access granted (negation)"));
		}
		else if (invert == 1 && st == 1)
		  log_msg((LOG_TRACE_LEVEL, "Access denied (negation)"));
		else {
		  if (errmsg != NULL)
			*errmsg = ds_xprintf("Line %d: access control test error",
								 tc->linenum);
		  goto fail;
		}

		break;

	  case D_END:
		/*
		 * The current region ends.
		 * This can match D_BEGIN, D_EXPAND, or D_FILTER/D_FILTERV.
		 */
		if (tc->stackp == NULL) {
		  if (errmsg != NULL)
			*errmsg = ds_xprintf("Line %d: DACS tag stack underflow",
								 tc->linenum);
		  goto fail;
		}

		if (!streq(tc->stackp->region, region)) {
		  if (errmsg != NULL)
			*errmsg = ds_xprintf("Line %d: DACS tag end mismatch",
								 tc->linenum);
		  goto fail;
		}

		log_msg((LOG_TRACE_LEVEL, "End of region \"%s\"", region));

		if (tc->stackp != NULL && tc->stackp->directive == D_EVAL) {
		  if ((buf = ds_buf(tc->stackp->diverted)) != NULL
			  && *buf != '\0') {
			/*
			 * This is done only for its side effects and explicit output
			 * via tprintf().
			 */
			tc->env->namespaces = tc->stackp->var_context;
			rc = acs_expr_string_env(buf, tc->env, &result_str);
			if (acs_expr_error_occurred(rc)) {
			  if (errmsg != NULL)
				*errmsg
				  = ds_xprintf("Line %d: expression evaluation error: \"%s\"",
							   tc->linenum, expr_attr);
			  goto fail;
			}
			if (tc->stackp->saved_attrs == NULL)
			  var_ns_delete(&tc->env->namespaces, "Attr");
			else
			  var_ns_replace(&tc->env->namespaces, "Attr",
							 tc->stackp->saved_attrs);
		  }

		  if (tc->stackp->directive == D_EVAL) {
			if (tc->stackp->prev != NULL)
			  tc->stackp->prev->var_context = tc->env->namespaces;
		  }
		  else {
			/* XXX */
			ds_asprintf(tc->ds_out, "%s%s",
						result_str,
						(tc->stackp->directive == D_FILTER) ? "\n" : "");
		  }
		  ds_free(tc->stackp->diverted);
		}
		else if (tc->stackp->expanding && !tc->stackp->skipping) {
		  Ds *ds;

		  /* Is this the end of a filtered region? */
		  buf = ds_buf(tc->stackp->diverted);
		  if (buf != NULL && *buf != '\0') {
			tc->env->namespaces = tc->stackp->var_context;

			if ((ds = acs_string_operand(buf, tc->env)) == NULL) {
			  if (errmsg != NULL)
				*errmsg = ds_xprintf("Line %d: expansion error: \"%s\"",
									 tc->linenum, buf);
			  goto fail;
			}
			buf = ds_buf(ds);
			if (tc->stackp->prev != NULL)
			  ds_asprintf(tc->stackp->prev->diverted, "%s", buf);
			else
			  ds_asprintf(tc->ds_out, "%s", buf);
		  }
		  ds_free(tc->stackp->diverted);
		}
		else
		  log_msg((LOG_TRACE_LEVEL, "End of region \"%s\"", region));

		pop_stack(tc);

		break;

	  case D_EXPAND:
	  case D_INSERT:
	  case D_INSERTV:
		/*
		 * Insert content from one of: file, URI, or expression.
		 */
		dcount = 0;
		if ((filename_attr = kwv_lookup_value(kwv_attrs, "filename")) != NULL)
		  dcount++;

		if ((expr_attr = kwv_lookup_value(kwv_attrs, "expr")) != NULL)
		  dcount++;

		if ((uri_attr = kwv_lookup_value(kwv_attrs, "uri")) != NULL) {
		  char *p;

		  dcount++;
		  if ((p = kwv_lookup_value(kwv_attrs, "method")) != NULL) {
			if ((method = http_string_to_method(p)) == HTTP_UNKNOWN_METHOD) {
			  if (errmsg != NULL)
				*errmsg = ds_xprintf("Line %d: unrecognized HTTP method",
									 tc->linenum);
			  goto fail;
			}
		  }
		  else
			method = HTTP_GET_METHOD;
		}

		if (directive == D_EXPAND && dcount == 0) {
		  /* Require matching D_END... */
		}
		else if (dcount != 1) {
		  if (errmsg != NULL)
			*errmsg = ds_xprintf("Line %d: invalid directive", tc->linenum);
		  goto fail;
		}

		if (uri_attr != NULL) {
		  Uri *uri;

		  if ((uri = uri_parse(uri_attr)) == NULL) {
			if (errmsg != NULL)
			  *errmsg = ds_xprintf("Line %d: invalid URI: %s",
								   tc->linenum, uri_attr);
			goto fail;
		  }
		  if (streq(uri->scheme, "file")) {
			if (uri->host != NULL || uri->port_given != NULL) {
			  if (errmsg != NULL)
				*errmsg = ds_xprintf("Line %d: invalid URI: %s",
									 tc->linenum, uri_attr);
			  goto fail;
			}

			filename_attr = uri->path;
			uri_attr = NULL;
		  }
		  else if (!streq(uri->scheme, "http") &&
				   !streq(uri->scheme, "https")) {
			if (errmsg != NULL)
			  *errmsg = ds_xprintf("Line %d: invalid URI scheme: %s",
								   tc->linenum, uri_attr);
			goto fail;
		  }
		}

		if (filename_attr != NULL) {
		  if (*filename_attr != '/' && *tc->docs != '\0') {
			if (errmsg != NULL)
			  *errmsg = ds_xprintf("Line %d: invalid directive", tc->linenum);
			goto fail;
		  }
		}

		if (tc->stack_depth == tc->max_stack_depth) {
		  if (errmsg != NULL)
			*errmsg = ds_xprintf("Line %d: DACS tag stack overflow",
								 tc->linenum);
		  goto fail;
		}

		stack_el
		  = push_stack(tc, directive, region, kwv_attrs, 1, expand, NULL);

		if (directive == D_EXPAND)
		  opname = "expand";
		else if (directive == D_INSERT)
		  opname = "insert";
		else
		  opname = "insertv";

		log_msg((LOG_TRACE_LEVEL, "%s: check if \"%s\" can access \"%s\"",
				 opname, username, region));

		st = region_test(tc, region, kwv_attrs, kwv_conf, kwv_dacs, opname,
						 invert, tc->acls, object_name, idents, errmsg);

		granted = 0;
		if (invert == 0 && st == 1) {
		  granted = 1;
		  log_msg((LOG_TRACE_LEVEL, "Access granted"));
		}
		else if (invert == 0 && st == 0)
		  log_msg((LOG_TRACE_LEVEL, "Access denied"));
		else if (invert == 1 && st == 0) {
		  granted = 1;
		  log_msg((LOG_TRACE_LEVEL, "Access granted (negation)"));
		}
		else if (invert == 1 && st == 1)
		  log_msg((LOG_TRACE_LEVEL, "Access denied (negation)"));
		else {
		  if (errmsg != NULL)
			*errmsg = ds_xprintf("Line %d: access control test error",
								 tc->linenum);
		  goto fail;
		}

		if (directive == D_EXPAND && dcount == 0 && granted)
		  stack_el->skipping = 0;

		if (dcount && granted) {
		  /* Get the content to be inserted. */
		  stack_el->skipping = 0;

		  if (filename_attr != NULL) {
			char *fn;

			if (*filename_attr == '/' || tc->insert_dir == NULL)
			  fn = filename_attr;
			else
			  fn = ds_xprintf("%s/%s", tc->insert_dir, filename_attr);

			if (load_file(fn, &buf, NULL) == -1) {
			  if (errmsg != NULL)
				*errmsg = ds_xprintf("Line %d: cannot load file: %s",
									 tc->linenum, fn);
			  goto fail;
			}
		  }
		  else if (uri_attr != NULL) {
			int argnum, reply_len;

			argnum = 0;
			if (http_invoke(uri_attr, method, HTTP_SSL_URL_SCHEME,
							argnum, NULL, NULL, NULL, &buf, &reply_len,
							NULL, NULL) == -1) {
			  if (errmsg != NULL)
				*errmsg = buf;
			  goto fail;
			}
		  }
		  else if (expr_attr != NULL) {
			rc = eval(expr_attr, kwv_attrs, &tc->env->namespaces, &buf);
			if (acs_expr_error_occurred(rc)) {
			  if (errmsg != NULL)
				*errmsg
				  = ds_xprintf("Line %d: expression evaluation error: \"%s\"",
							   tc->linenum, expr_attr);
			  goto fail;
			}
		  }

		  /*
		   * Recursively process the new input, if enabled and there is some.
		   */
		  if (recurse && buf != NULL) {
			Ds *ds_in, *ds_out;
			Transform_config *ntc;

			ds_in = ds_init(NULL);
			dsio_set(ds_in, NULL, buf, strlen(buf), 1);
			ds_out = ds_init(NULL);
			ntc = transform_reconfig(tc, ds_in, ds_out);
			ntc->env->namespaces = tc->stackp->var_context;
			if ((st = transform(ntc, object_name, idents, errmsg)) != 0)
			  goto fail;
			buf = ds_buf(stack_el->diverted);
			free(ntc);
		  }

		  /*
		   * Expand the content (for variable references), if necessary.
		   */
		  if (tc->stackp->expanding && buf != NULL) {
			Ds *ds;

			tc->env->namespaces = tc->stackp->var_context;

			if ((ds = acs_string_operand(buf, tc->env)) == NULL) {
			  if (errmsg != NULL)
				*errmsg = ds_xprintf("Line %d: expansion error: \"%s\"",
									 tc->linenum, buf);
			  goto fail;
			}
			buf = ds_buf(ds);
			if (tc->stackp->prev != NULL)
			  ds_asprintf(tc->stackp->prev->diverted, "%s", buf);
			else
			  ds_asprintf(tc->ds_out, "%s", buf);
			ds_free(tc->stackp->diverted);
		  }
		  else {
			/*
			 * Insert the content.
			 */
			if (tc->stackp->prev != NULL)
			  ds_asprintf(tc->stackp->prev->diverted, "%s", buf);
			else
			  ds_asprintf(tc->ds_out, "%s%s",
						  buf, (directive == D_INSERT) ? "\n" : "");
		  }
		}

		if (dcount)
		  pop_stack(tc);

		break;

	  case D_ID:
		kwv_merge(kwv_attrs, tc->global_attrs, KWV_NO_DUPS);

		st = region_test(tc, region, kwv_attrs, kwv_conf, kwv_dacs, "id",
						 invert, tc->acls, object_name, idents, errmsg);

		granted = 0;
		if (invert == 0 && st == 1) {
		  granted = 1;
		  log_msg((LOG_TRACE_LEVEL, "Access granted"));
		}
		else if (invert == 0 && st == 0)
		  log_msg((LOG_TRACE_LEVEL, "Access denied"));
		else if (invert == 1 && st == 0) {
		  granted = 1;
		  log_msg((LOG_TRACE_LEVEL, "Access granted (negation)"));
		}
		else if (invert == 1 && st == 1)
		  log_msg((LOG_TRACE_LEVEL, "Access denied (negation)"));
		else {
		  if (errmsg != NULL)
			*errmsg = ds_xprintf("Line %d: access control test error",
								 tc->linenum);
		  goto fail;
		}

		if (granted) {
		  struct utsname utsname;
		  Ds *out;

		  if (tc->stackp != NULL && tc->stackp->diverted != NULL)
			out = tc->stackp->diverted;
		  else
			out = tc->ds_out;
		  uname(&utsname);
		  ds_asprintf(out, "%sGenerated %s by %s %s on %s %s",
					  tc->directive_prefix,
					  make_short_local_date_string(NULL),
					  log_module_name,
					  dacs_version_string(),
					  utsname.nodename,
					  suffix);
		}
		break;

	  case D_DEBUG:
		kwv_merge(kwv_attrs, tc->global_attrs, KWV_NO_DUPS);

		st = region_test(tc, region, kwv_attrs, kwv_conf, kwv_dacs, "debug",
						 invert, tc->acls, object_name, idents, errmsg);

		granted = 0;
		if (invert == 0 && st == 1) {
		  granted = 1;
		  log_msg((LOG_TRACE_LEVEL, "Access granted"));
		}
		else if (invert == 0 && st == 0)
		  log_msg((LOG_TRACE_LEVEL, "Access denied"));
		else if (invert == 1 && st == 0) {
		  granted = 1;
		  log_msg((LOG_TRACE_LEVEL, "Access granted (negation)"));
		}
		else if (invert == 1 && st == 1)
		  log_msg((LOG_TRACE_LEVEL, "Access denied (negation)"));
		else {
		  if (errmsg != NULL)
			*errmsg = ds_xprintf("Line %d: access control test error",
								 tc->linenum);
		  goto fail;
		}

		if (granted) {
		  int show_attrs, show_dacs, show_conf;
		  Ds *out;

		  show_attrs = show_dacs = show_conf = 1;
		  if ((p = kwv_lookup_value(kwv_attrs, "show")) != NULL) {
			show_attrs = show_dacs = show_conf = 0;
			if (strcaseeq(p, "Attrs"))
			  show_attrs = 1;
			else if (strcaseeq(p, "DACS"))
			  show_dacs = 1;
			else if (strcaseeq(p, "Conf"))
			  show_conf = 1;
			else if (strcaseeq(p, "all"))
			  show_attrs = show_dacs = show_conf = 1;
		  }
		  if ((p = kwv_lookup_value(kwv_attrs, "Attrs")) != NULL) {
			if (strcaseeq(p, "no"))
			  show_attrs = 0;
			else
			  show_attrs = 1;
		  }
		  if ((p = kwv_lookup_value(kwv_attrs, "Conf")) != NULL) {
			if (strcaseeq(p, "no"))
			  show_conf = 0;
			else
			  show_conf = 1;
		  }
		  if ((p = kwv_lookup_value(kwv_attrs, "DACS")) != NULL) {
			if (strcaseeq(p, "no"))
			  show_dacs = 0;
			else
			  show_dacs = 1;
		  }

		  if (tc->stackp != NULL && tc->stackp->diverted != NULL)
			out = tc->stackp->diverted;
		  else
			out = tc->ds_out;

		  ds_asprintf(out, "%sDebug:\n", tc->directive_prefix);
		  if (show_attrs && kwv_count(kwv_attrs, NULL) > 0)
			ds_asprintf(out, "Attrs:\n%s",
						kwv_buf(kwv_attrs, (int) '=', (int) '"'));
		  if (show_dacs && kwv_count(kwv_dacs, NULL) > 0)
			ds_asprintf(out, "DACS:\n%s",
						kwv_buf(kwv_dacs, (int) '=', (int) '"'));
		  if (show_conf && kwv_count(kwv_conf, NULL) > 0)
			ds_asprintf(out, "Conf:\n%s",
						kwv_buf(kwv_conf, (int) '=', (int) '"'));
		  ds_asprintf(out, "%s", suffix);
		}

		break;

	  case D_COMMENT:
		if (tc->stackp == NULL || !tc->stackp->skipping) {
		  Ds *out;

		  if (tc->stackp != NULL && tc->stackp->diverted != NULL)
			out = tc->stackp->diverted;
		  else
			out = tc->ds_out;

		  ds_asprintf(out, "%s%s", line, suffix);
		  elided = 0;
		}

		break;

	  case D_EVAL:
		if (tc->stack_depth == tc->max_stack_depth) {
		  if (errmsg != NULL)
			*errmsg = ds_xprintf("Line %d: DACS tag stack overflow",
								 tc->linenum);
		  goto fail;
		}

		stack_el
		  = push_stack(tc, directive, region, kwv_attrs, 1, expand, NULL);

		log_msg((LOG_TRACE_LEVEL, "filter: check if \"%s\" can access \"%s\"",
				 username, region));

		st = region_test(tc, region, kwv_attrs, kwv_conf, kwv_dacs, "eval",
						 invert, tc->acls, object_name, idents, errmsg);

		if (invert == 0 && st == 1) {
		  tc->stackp->skipping = 0;
		  log_msg((LOG_TRACE_LEVEL, "Access granted"));
		}
		else if (invert == 0 && st == 0)
		  log_msg((LOG_TRACE_LEVEL, "Access denied"));
		else if (invert == 1 && st == 0) {
		  tc->stackp->skipping = 0;
		  log_msg((LOG_TRACE_LEVEL, "Access granted (negation)"));
		}
		else if (invert == 1 && st == 1)
		  log_msg((LOG_TRACE_LEVEL, "Access denied (negation)"));
		else {
		  if (errmsg != NULL)
			*errmsg = ds_xprintf("Line %d: access control test error",
								 tc->linenum);
		  goto fail;
		}

		break;

	  case D_SET:
		kwv_merge(tc->global_attrs, kwv_attrs, KWV_REPLACE_DUPS);

		st = region_test(tc, region, kwv_attrs, kwv_conf, kwv_dacs, "set",
						 invert, tc->acls, object_name, idents, errmsg);

		granted = 0;
		if (invert == 0 && st == 1) {
		  granted = 1;
		  log_msg((LOG_TRACE_LEVEL, "Access granted"));
		}
		else if (invert == 0 && st == 0)
		  log_msg((LOG_TRACE_LEVEL, "Access denied"));
		else if (invert == 1 && st == 0) {
		  granted = 1;
		  log_msg((LOG_TRACE_LEVEL, "Access granted (negation)"));
		}
		else if (invert == 1 && st == 1)
		  log_msg((LOG_TRACE_LEVEL, "Access denied (negation)"));
		else {
		  if (errmsg != NULL)
			*errmsg = ds_xprintf("Line %d: access control test error",
								 tc->linenum);
		  goto fail;
		}

		if (granted)
		  kwv_merge(tc->global_attrs, kwv_attrs, KWV_NO_DUPS);

		break;

	  case D_FILTER:
	  case D_FILTERV:
		/*
		 * If document content is included, it will either be fed to an
		 * expression or be evaluated as an expression.
		 */
		dcount = 0;
		if ((expr_attr = kwv_lookup_value(kwv_attrs, "expr")) != NULL)
		  dcount++;

		if ((uri_attr = kwv_lookup_value(kwv_attrs, "uri")) != NULL) {
		  char *p;

		  dcount++;
		  if ((p = kwv_lookup_value(kwv_attrs, "method")) != NULL) {
			if ((method = http_string_to_method(p)) == HTTP_UNKNOWN_METHOD) {
			  if (errmsg != NULL)
				*errmsg = ds_xprintf("Line %d: unrecognized HTTP method",
									 tc->linenum);
			  goto fail;
			}
		  }
		  else
			method = HTTP_POST_METHOD;
		}

		if (dcount != 1) {
		  if (errmsg != NULL)
			*errmsg = ds_xprintf("Line %d: invalid directive", tc->linenum);
		  goto fail;
		}

		if (uri_attr != NULL) {
		  if (errmsg != NULL)
			*errmsg = ds_xprintf("Line %d: unimplemented directive",
								 tc->linenum);
		  goto fail;
		}

		if (tc->stack_depth == tc->max_stack_depth) {
		  if (errmsg != NULL)
			*errmsg = ds_xprintf("Line %d: DACS tag stack overflow",
								 tc->linenum);
		  goto fail;
		}

		stack_el
		  = push_stack(tc, directive, region, kwv_attrs, 1, expand, NULL);


		log_msg((LOG_TRACE_LEVEL, "filter: check if \"%s\" can access \"%s\"",
				 username, region));

		if (directive == D_FILTER)
		  opname = "filter";
		else
		  opname = "filterv";
		st = region_test(tc, region, kwv_attrs, kwv_conf, kwv_dacs, opname,
						 invert, tc->acls, object_name, idents, errmsg);

		if (invert == 0 && st == 1) {
		  tc->stackp->skipping = 0;
		  log_msg((LOG_TRACE_LEVEL, "Access granted"));
		}
		else if (invert == 0 && st == 0)
		  log_msg((LOG_TRACE_LEVEL, "Access denied"));
		else if (invert == 1 && st == 0) {
		  tc->stackp->skipping = 0;
		  log_msg((LOG_TRACE_LEVEL, "Access granted (negation)"));
		}
		else if (invert == 1 && st == 1)
		  log_msg((LOG_TRACE_LEVEL, "Access denied (negation)"));
		else {
		  if (errmsg != NULL)
			*errmsg = ds_xprintf("Line %d: access control test error",
								 tc->linenum);
		  goto fail;
		}

		if (tc->stackp->skipping == 0) {
		  tc->stackp->diverted = ds_init(NULL);
		  /*
		  tc->stackp->attrs = kwv_attrs;
		  */
		}

		break;

	  case D_NONE:
		/*
		 * The directive does not contain any attributes or does not contain
		 * a recognized attribute name.
		 */
		if (errmsg != NULL)
		  *errmsg = ds_xprintf("Line %d: unrecognized directive", tc->linenum);
		goto fail;
		/*NOTREACHED*/

	  default:
	  case D_ERROR:
		/* An invalid directive. */
		if (errmsg != NULL)
		  *errmsg = ds_xprintf("Line %d: %s", tc->linenum, errmsg);
		goto fail;
		/*NOTREACHED*/
	  }
	}
	else {
	  /*
	   * This is not a directive, just content.
	   * Emit it if we're emitting, otherwise emit an annotation or nothing
	   * at all in its place.
	   */
	  if (tc->stackp == NULL || !tc->stackp->skipping) {
		if (tc->stackp != NULL && tc->stackp->diverted != NULL)
		  ds_asprintf(tc->stackp->diverted, "%s", line);
		else
		  ds_asprintf(tc->ds_out, "%s", line);
		elided = 0;
	  }
	  else if (tc->annotation != NULL) {
		if (elided == 0) {
		  /* XXX This needs to depend on the document's Content-Type */
		  ds_asprintf(tc->ds_out, "<br/>%s<br/>\n", tc->annotation);
		}
		elided = 1;
	  }
	}
	ds_reset(tc->ds_in);
  }

  if (tc->stackp != current_stackp) {
	if (errmsg != NULL)
	  *errmsg = "Directive tags are unbalanced";
	goto fail;
  }

  return(0);

 fail:

  return(-1);
}

/*
 * Transform either DOCNAME (a local file) or the document at DOCURI.
 * DOCNAME may be "/dev/stdin" with the obvious behaviour.
 * NAME is the object name to use instead of DOCNAME.
 * If transform_docs is a non-empty string, it means that DOCNAME is
 * a subdirectory of transform_docs; i.e., DOCNAME is being served from
 * a particular area in the file system.
 *
 * DOCURI is retrieved via the GET method.
 *
 * Take care with DOCNAME, especially wrt a "/../" component, which might be
 * used in an attempt to escape from the transform_docs directory.
 *
 * The MIME type of the emitted document is CONTENT_TYPE, if provided;
 * otherwise an attempt is made to deduce the type from the filename suffix of
 * DOCNAME or the path suffix of DOCURI using Apache's mime.types file.
 * If that fails, we fall back on a default.
 *
 * Return 0 if all goes well, -1 otherwise (and set ERRMSG).
 */
static int
do_transform(Transform_config *tc, char *docname, char *docuri, char *name,
			 char *idents, char *content_type, char **errmsg)
{
  int st;
  char *doctype, *ext, *object_name;
  char *path;
  Dsvec *mime_types;
  FILE *fp;
  Mime_file_type *mft;

  fp = NULL;
  path = NULL;

  if (docname != NULL) {
	/* XXX disallow ".." within DOCNAME too */
	if (strstr(docname, "/../")
		|| strsuffix(docname, strlen(docname), "/") != NULL) {
	  if (errmsg != NULL)
		*errmsg = "Invalid DOC name";
	  goto fail;
	}
	if (*tc->docs == '\0')
	  path = docname;
	else
	  path = ds_xprintf("%s/%s", tc->docs, docname + 1);
	if ((fp = fopen(path, "r")) == NULL) {
	  if (errmsg != NULL)
		*errmsg = ds_xprintf("Cannot load \"%s\"", path);
	  goto fail;
	}
	dsio_set(tc->ds_in, fp, NULL, 0, 0);
	log_msg((LOG_TRACE_LEVEL, "File is %s", path));
  }
  else {
	int argnum, reply_len;
	char *reply;
	Uri *uri;

	if ((uri = uri_parse(docuri)) == NULL) {
	  if (errmsg != NULL)
		*errmsg = ds_xprintf("Invalid URI: %s", docuri);
	  goto fail;
	}
	path = uri->path;
	argnum = 0;
	reply_len = -1;
	if (http_invoke(docuri, HTTP_GET_METHOD, HTTP_SSL_URL_SCHEME,
					argnum, NULL, NULL, NULL, &reply, &reply_len,
					NULL, NULL) == -1) {
	  if (errmsg != NULL)
		*errmsg = reply;
	  goto fail;
	}
	dsio_set(tc->ds_in, NULL, reply, reply_len, 1);
  }

  if (name == NULL) {
	if (docname != NULL)
	  object_name = docname;
	else
	  object_name = path;
  }
  else
	object_name = name;

  if (object_name == NULL) {
	if (errmsg != NULL)
	  *errmsg = "No name is available for the input document";
	goto fail;
  }
  if (*object_name != '/')
	object_name = ds_xprintf("/%s", object_name);

  /* Figure out the right content type to emit, if any. */
  if (content_type != NULL) {
	if (*content_type == '\0')
	  doctype = NULL;
	else
	  doctype = content_type;
  }
  else {
	/*
	 * The document type-by-extension will be lost because the document
	 * is being fetched indirectly, so we need to set it explicitly.
	 */
	doctype = "text/html";
	if ((ext = strextname(path)) != NULL) {
	  if ((mime_types
		   = mime_get_file_types(ds_xprintf("%s/conf/mime.types",
											APACHE_HOME))) != NULL) {
		if ((mft = mime_find_file_type(mime_types, ext + 1)) != NULL)
		  doctype = mft->type_name;
	  }
	  else {
		if (path != NULL) {
		  if (strcaseeq(ext, ".xml"))
			doctype = "text/xml";
		  else if (strcaseeq(ext, ".txt"))
			doctype = "text/plain";
		}
	  }
	}
  }

  if (doctype != NULL)
	printf("Content-Type: %s\n\n", doctype);

  st = transform(tc, object_name, idents, errmsg);
  if (fp != NULL)
	fclose(fp);

  if (ds_len(tc->ds_out) > 0)
	printf("%s", ds_buf(tc->ds_out));

  return(st);

 fail:
  if (fp != NULL)
	fclose(fp);

  return(-1);
}

static void
dacs_usage(void)
{

  fprintf(stderr, "Usage: dacstransform [dacsoptions] [flags] [- | file]\n");
  fprintf(stderr, "flags are composed from:\n");
  fprintf(stderr, "-admin:              following idents are DACS admins\n");
  fprintf(stderr, "-ct str:             the MIME content type string\n");
  fprintf(stderr, "-docs dir:           directory containing the documents\n");
  fprintf(stderr, "-f:                  don't map the file's location\n");
  fprintf(stderr, "-F sep:              use sep as the field separator for roles\n");
  fprintf(stderr, "-fd domain:          the federation domain\n");
  fprintf(stderr, "-fh hostname:        the hostname\n");
  fprintf(stderr, "-fj jurname:         the jurisdiction name\n");
  fprintf(stderr, "-fn fedname:         the federation name\n");
  fprintf(stderr, "-h|-help:            show this help blurb\n");
  fprintf(stderr, "-i ident:            use ident\n");
  fprintf(stderr, "-icgi:               use REMOTE_USER for username\n");
  fprintf(stderr, "-icgig:              like -icgi but also add roles\n");
  fprintf(stderr, "-ieuid:              add the euid to set of identities\n");
  fprintf(stderr, "-ieuidg:             like -ieuid but also add its groups\n");
  fprintf(stderr, "-il ident:           use ident, a local name\n");
  fprintf(stderr, "-ilg ident:          use local ident with its Unix groups\n");
  fprintf(stderr, "-insert dir:         directory containing inserted documents\n");
  fprintf(stderr, "-iuid:               add the uid to set of identities\n");
  fprintf(stderr, "-iuidg:              like -iuid but also add its groups\n");
  fprintf(stderr, "-lg:                 add Unix groups to roles of locals\n");
  fprintf(stderr, "-name str:           the name of the input document\n");
  fprintf(stderr, "-prefix str:         the directive prefix string\n");
  fprintf(stderr, "-r|-rules rules_uri: where to look for rules\n");
  fprintf(stderr, "-roles roles_vfs:    where to look for roles\n");
  fprintf(stderr, "-rprefix regex:      the directive prefix, as a regex\n");
  fprintf(stderr, "-rsuffix regex:      the directive suffix, as a regex\n");
  fprintf(stderr, "-suffix str:         the directive suffix string\n");
  fprintf(stderr, "-var name=value:     set name to value in DACS namespace\n");
  fprintf(stderr, "-x:                  run as an app, not a web service\n");
  fprintf(stderr, "--:                  end of flag arguments\n");
  fprintf(stderr, "\n");
  fprintf(stderr, "The standard input is read if no file is specified or\n");
  fprintf(stderr, "if '-' is specified.  This implies the -f flag.\n");
  fprintf(stderr, "Unless the -f flag appears, a file argument is mapped\n");
  fprintf(stderr, "to the document directory.\n");
  fprintf(stderr, "The default document directory is \"%s\"\n",
		  DACS_TRANSFORM_DOCS);
  fprintf(stderr, "The default ACL URI is \"%s\"\n",
		  DACS_TRANSFORM_ACLS);

  exit(1);
}

int
dacstransform_main(int argc, char **argv, int do_init, void *main_out)
{
  int exec_run, i, st;
  char *content_type, *doc, *errmsg, *idents, *name, *p, *remote_addr, *uri;
  char *acls, *annotation, *directive_prefix, *directive_suffix, *docs;
  char *insert_dir, *regex_prefix, *regex_suffix;
  DACS_app_type app_type;
  Kwv *kwv, *kwv_conf, *kwv_dacs;
  Transform_config *tc, transform_config;
  Var_ns *env_ns;

  errmsg = "Internal error";

  tc = transform_init(&transform_config);
  acls = NULL;
  docs = NULL;
  insert_dir = NULL;
  content_type = NULL;
  directive_prefix = NULL;
  directive_suffix = NULL;
  annotation = NULL;
  regex_prefix = NULL;
  regex_suffix = NULL;

  doc = uri = NULL;
  name = NULL;
  idents = NULL;

  /* Tentatively... */
  if ((remote_addr = getenv("REMOTE_ADDR")) == NULL)
	app_type = DACS_STANDALONE;
  else
	app_type = DACS_WEB_SERVICE;

#ifdef NOTDEF
  for (i = 0; i < argc; i++)
    fprintf(stderr, "%d: \"%s\"\n", i, argv[i]);
#endif

  /*
   * If run as a script via:
   *   #! .../dacs
   * then everything that follows the command name on the line is passed as
   * a single argument to this program, followed by the name of the file
   * being executed, and any command line arguments as individual arguments.
   * See execve(2).
   */
  exec_run = 0;
  if (streq(argv[0], "dacstransform") || streq(argv[0], "transform")
	  || strsuffix(argv[0], strlen(argv[0]), "/dacstransform") != NULL
	  || strsuffix(argv[0], strlen(argv[0]), "/transform") != NULL) {
	if ((argc == 2 && argv[1][0] != '-')
		|| (argc == 3 && argv[1][0] == '-')) {
      Dsvec *av;

      exec_run = 1;
	  av = ds_mkargv_add(NULL, argv[0]);
	  if (argc == 3) {
		ds_mkargv(av, argv[1], NULL);
		ds_mkargv_add(av, argv[2]);
	  }
	  else
		ds_mkargv_add(av, argv[1]);
      argc = dsvec_len(av) - 1;
      argv = (char **) dsvec_base(av);
	  doc = "/dev/stdin";
	  app_type = DACS_STANDALONE;
    }
  }

#ifdef NOTDEF
  for (i = 0; i < argc; i++)
    fprintf(stderr, "%d: \"%s\"\n", i, argv[i]);
#endif

  for (i = 1; argv[i] != NULL; i++) {
	if (streq(argv[i], "-x")) {
	  app_type = DACS_STANDALONE;
	  break;
	}
  }
	
  if (app_type == DACS_STANDALONE)
	log_module_name = "dacstransform";
  else
	log_module_name = "dacs_transform";

  if (dacs_init(app_type, &argc, &argv, &kwv, &errmsg) == -1) {
  fail:
	if (app_type == DACS_STANDALONE) {
	  if (errmsg)
		fprintf(stderr, "Error: %s\n", errmsg);
	  else
		fprintf(stderr, "An error occurred\n");

	  exit(1);
	}

	if (errmsg)
	  log_msg((LOG_ERROR_LEVEL, "Error: %s", errmsg));

	emit_html_header(stdout, NULL);
	printf("<p>An error occurred during the service request.\n");
	if (errmsg != NULL)
	  printf("<p>Reason: %s.\n", errmsg);
	emit_html_trailer(stdout);

	exit(1);
  }

  kwv_conf = kwv_init(10);
  kwv_dacs = kwv_dacsoptions;
  kwv_set_mode(kwv_dacs, "dr");
  kwv_set_mode(kwv, "+i");

  if (app_type == DACS_STANDALONE) {
	for (i = 1; i < argc; i++) {
	  if (argv[i][0] != '-')
		break;

	  st = set_ident_config(&i, argv, kwv_dacs, kwv_conf, &idents, &errmsg);
	  if (st == -1)
		goto fail;
	  if (st > 0) {
		i--;
		continue;
	  }
	  else if (streq(argv[i], "-docs")) {
		if (++i == argc) {
		  errmsg = ds_xprintf("Document VFS expected after %s", argv[i - 1]);
		  goto fail;
		}
		if (docs != NULL) {
		  errmsg = ds_xprintf("Duplicate %s specification", argv[i - 1]);
		  goto fail;
		}
		docs = argv[i];
	  }
	  else if (streq(argv[i], "-insert")) {
		if (++i == argc) {
		  errmsg = ds_xprintf("Document VFS expected after %s", argv[i - 1]);
		  goto fail;
		}
		if (insert_dir != NULL) {
		  errmsg = ds_xprintf("Duplicate %s specification", argv[i - 1]);
		  goto fail;
		}
		insert_dir = argv[i];
	  }
	  else if (streq(argv[i], "-f"))
		docs = "";
	  else if (streq(argv[i], "-x")) {
		/* Ignore here */
	  }
	  else if (streq(argv[i], "-h") || streq(argv[i], "-help")) {
		dacs_usage();
		/*NOTREACHED*/
	  }
	  else if (streq(argv[i], "-name")) {
		if (++i == argc) {
		  errmsg = ds_xprintf("Name string expected after %s", argv[i - 1]);
		  goto fail;
		}
		if (name != NULL) {
		  errmsg = ds_xprintf("Duplicate %s specification", argv[i - 1]);
		  goto fail;
		}
		name = argv[i];
	  }
	  else if (streq(argv[i], "-prefix")) {
		if (++i == argc) {
		  errmsg = ds_xprintf("Prefix string expected after %s", argv[i - 1]);
		  goto fail;
		}
		if (directive_prefix != NULL) {
		  errmsg = ds_xprintf("Duplicate %s specification", argv[i - 1]);
		  goto fail;
		}
		directive_prefix = argv[i];
	  }
	  else if (streq(argv[i], "-rprefix")) {
		if (++i == argc) {
		  errmsg = ds_xprintf("Regex prefix string expected after %s",
							  argv[i - 1]);
		  goto fail;
		}
		if (regex_prefix != NULL) {
		  errmsg = ds_xprintf("Duplicate %s specification", argv[i - 1]);
		  goto fail;
		}
		regex_prefix = argv[i];
	  }
	  else if (streq(argv[i], "-rsuffix")) {
		if (++i == argc) {
		  errmsg = ds_xprintf("Regex suffix string expected after %s",
							  argv[i - 1]);
		  goto fail;
		}
		if (regex_suffix != NULL) {
		  errmsg = ds_xprintf("Duplicate %s specification", argv[i - 1]);
		  goto fail;
		}
		regex_suffix = argv[i];
	  }
	  else if (streq(argv[i], "-suffix")) {
		if (++i == argc) {
		  errmsg = ds_xprintf("Suffix string expected after %s", argv[i - 1]);
		  goto fail;
		}
		if (directive_suffix != NULL) {
		  errmsg = ds_xprintf("Duplicate %s specification", argv[i - 1]);
		  goto fail;
		}
		directive_suffix = argv[i];
	  }
	  else if (streq(argv[i], "-ct")) {
		if (++i == argc) {
		  errmsg = ds_xprintf("Content type string expected after %s",
							  argv[i - 1]);
		  goto fail;
		}
		if (content_type != NULL) {
		  errmsg = ds_xprintf("Duplicate %s specification", argv[i - 1]);
		  goto fail;
		}
		content_type = argv[i];
	  }
	  else if (streq(argv[i], "-r") || streq(argv[i], "-rules")) {
		Vfs_directive *vd;

		if (++i == argc) {
		  errmsg = ds_xprintf("Ruleset VFS expected after %s", argv[i - 1]);
		  goto fail;
		}
		if (acls != NULL) {
		  errmsg = ds_xprintf("Duplicate %s specification", argv[i - 1]);
		  goto fail;
		}
		if ((vd = vfs_uri_parse(argv[i], NULL)) == NULL) {
		  if (argv[i][0] != '/') {
			errmsg = "Invalid ruleset VFS";
			goto fail;
		  }
		  acls = ds_xprintf("[transform-acls]dacs-fs:%s", argv[i]);
		}
		else
		  acls = argv[i];
	  }
	  else if (streq(argv[i], "-var")) {
		char *def, *varname, *varvalue;

		if (++i == argc) {
		  errmsg = "Variable name=value required after -var";
		  goto fail;
		}
		def = argv[i];

		if (kwv_parse_str(def, &varname, &varvalue) == -1
			|| !var_ns_is_valid_varname(varname, NULL)) {
		  errmsg = "Usage: -var name=value";
		  goto fail;
		}

		if (kwv_add_nocopy(kwv_dacs, varname, varvalue) == NULL) {
		  errmsg = ds_xprintf("Can't initialize: %s", def);
		  goto fail;
		}
	  }
	  else if (streq(argv[i], "--")) {
		i++;
		break;
	  }
	  else
		break;
	}

	if (argv[i] == NULL || streq(argv[i], "-")) {
	  doc = "/dev/stdin";
	  docs = "";
	}
	else if (argv[i + 1] != NULL) {
	  errmsg = "Only one document may be specified";
	  goto fail;
	}
	else {
	  if (strcaseprefix(argv[i], "http://") ||
		  strcaseprefix(argv[i], "https://"))
		uri = argv[i];
	  else
		doc = argv[i];
	}

	if (docs == NULL)
	  docs = "";
	if (content_type == NULL)
	  content_type = "";
  }
  else {
	doc = kwv_lookup_value(kwv, "DOC");
	uri = kwv_lookup_value(kwv, "DOCURI");	
	/* An ANNOTATE argument overrides any other annotation config. */
	if ((p = kwv_lookup_value(kwv, "ANNOTATE")) != NULL && strcaseeq(p, "yes"))
	  annotation = DEFAULT_HTML_ANNOTATION;

	content_type = kwv_lookup_value(kwv, "CONTENT_TYPE");
  }

  if (doc == NULL && uri == NULL) {
	errmsg = "DOC or DOCURI argument is required";
    goto fail;
  }

  if (doc != NULL) {
	if (uri != NULL) {
	  errmsg = "Only one of DOC or DOCURI can be specified";
	  goto fail;
	}
	if (app_type == DACS_WEB_SERVICE && *doc != '/') {
	  errmsg = "DOC must be an absolute path";
	  goto fail;
	}
  }
  else {
	if (strcaseprefix(uri, "http://") == NULL
		&& strcaseprefix(uri, "https://") == NULL) {
	  errmsg = "Unrecognized URI scheme";
	  goto fail;
	}
  }

  if (acls != NULL)
	tc->acls = acls;
  if (docs != NULL)
	tc->docs = docs;
  if (directive_prefix != NULL)
	tc->directive_prefix = directive_prefix;
  if (directive_suffix != NULL)
	tc->directive_suffix = directive_suffix;
  if (regex_prefix != NULL)
	tc->regex_prefix = regex_prefix;
  if (regex_suffix != NULL)
	tc->regex_suffix = regex_suffix;

  tc->insert_dir = insert_dir;

  log_msg((LOG_TRACE_LEVEL, "transform_docs=\"%s\"", non_null(tc->docs)));
  log_msg((LOG_TRACE_LEVEL, "transform_acls=\"%s\"", non_null(tc->acls)));
  log_msg((LOG_TRACE_LEVEL, "directive_prefix=\"%s\"",
		   non_null(tc->directive_prefix)));
  log_msg((LOG_TRACE_LEVEL, "directive_suffix=\"%s\"",
		   non_null(tc->directive_suffix)));
  log_msg((LOG_TRACE_LEVEL, "regex_prefix=\"%s\"",
		   non_null(tc->regex_prefix)));
  log_msg((LOG_TRACE_LEVEL, "regex_suffix=\"%s\"",
		   non_null(tc->regex_suffix)));

  if (idents == NULL) {
	char *user;

	if ((user = getenv("REMOTE_USER")) != NULL
		&& *user != '\0' && !streq(user, "unauth")) {
	  char *roles;

	  roles = getenv("DACS_ROLES");
	  idents = make_ident(user, roles, NULL, 0, remote_addr);
	}
  }

  var_ns_new(&tc->env->namespaces, "DACS", kwv_dacs);
  if (dacs_conf != NULL && dacs_conf->conf_var_ns != NULL)
	var_ns_new(&tc->env->namespaces, dacs_conf->conf_var_ns->ns,
			   dacs_conf->conf_var_ns->kwv);

  if ((env_ns = var_ns_from_env("Env")) != NULL)
	var_ns_new(&tc->env->namespaces, "Env", env_ns->kwv);

  if (do_transform(tc, doc, uri, name, idents, content_type, &errmsg) == -1)
    goto fail;

  exit(0);
}

#else

int
main(int argc, char **argv)
{
  int rc;

  if ((rc = dacstransform_main(argc, argv, 1, NULL)) == 0)
    exit(0);

  exit(1);
}

#endif
