diff -Nbru clearsilver-0.3/Makefile clearsilver-0.4/Makefile
--- clearsilver-0.3/Makefile	Tue Aug  7 14:57:45 2001
+++ clearsilver-0.4/Makefile	Fri Jan 11 15:45:44 2002
@@ -3,8 +3,6 @@
 #
 # Copyright (C) 2001 Neotonic and Brandon Long
 #
-# This file is licensed under the terms of the FSF's LGPL, or
-# Library General Public License.
 #
 
 
@@ -71,14 +69,22 @@
 		mkdir -p $$mdir; \
 	done
 
-CS_DISTDIR = clearsilver-0.1
-CS_LABEL = CLEARSILVER-0_1
-CS_FILES = LICENSE CS_LICENSE rules.mk Makefile util cs cgi python scripts
-cs_dist: distclean
+CS_DISTDIR = clearsilver-0.3
+CS_LABEL = CLEARSILVER-0_3
+CS_FILES = LICENSE CS_LICENSE rules.mk Makefile util cs cgi python scripts mod_ecs
+cs_dist:
 	rm -rf $(CS_DISTDIR)
 	cvs -q tag -F $(CS_LABEL) $(CS_FILES)
 	mkdir -p $(CS_DISTDIR)
 	cvs -z3 -q export -r $(CS_LABEL) -d $(CS_DISTDIR) neotonic
 	$(MAKE) -C $(CS_DISTDIR) man
-	tar chozf clearsilver-0.1.tar.gz $(CS_DISTDIR)
+	tar chozf $(CS_DISTDIR).tar.gz $(CS_DISTDIR)
 	
+TRAKKEN_DISTDIR = trakken-0.55
+TRAKKEN_LABEL = TRAKKEN_0_55
+trakken_dist:
+	rm -rf $(TRAKKEN_DISTDIR)
+	cvs -q tag -F $(TRAKKEN_LABEL)
+	mkdir -p $(TRAKKEN_DISTDIR)
+	cvs -z3 -q export -r $(TRAKKEN_LABEL) -d $(TRAKKEN_DISTDIR) neotonic
+	tar chozf $(TRAKKEN_DISTDIR).tar.gz $(TRAKKEN_DISTDIR)
diff -Nbru clearsilver-0.3/cgi/Makefile clearsilver-0.4/cgi/Makefile
--- clearsilver-0.3/cgi/Makefile	Sun Sep  9 16:53:10 2001
+++ clearsilver-0.4/cgi/Makefile	Fri Jan 11 15:42:08 2002
@@ -13,12 +13,13 @@
 STATIC_EXE = static.cgi
 STATIC_SRC = static.c
 STATIC_OBJ = $(STATIC_SRC:%.c=%.o)
+STATIC_CSO = $(STATIC_EXE:%.cgi=%.cso)
 
 CFLAGS += -I$(NEOTONIC_ROOT) -DHTML_COMPRESSION
 DLIBS += -lneo_cgi -lneo_cs -lneo_utl # -lefence
 LIBS += -L$(LIB_DIR) $(DLIBS)
 
-TARGETS = $(CGI_LIB) $(STATIC_EXE)
+TARGETS = $(CGI_LIB) $(STATIC_EXE) $(STATIC_CSO)
 
 all: $(TARGETS)
 
@@ -27,6 +28,9 @@
 
 $(STATIC_EXE): $(STATIC_OBJ) $(DEP_LIBS)
 	$(LD) $@ $(STATIC_OBJ) $(LIBS)
+
+$(STATIC_CSO): $(STATIC_OBJ) $(DEP_LIBS)
+	$(LDSHARED) -o $@ $(STATIC_OBJ) $(LIBS)
 
 clean:
 	$(RM) *.o
diff -Nbru clearsilver-0.3/cgi/cgiwrap.c clearsilver-0.4/cgi/cgiwrap.c
--- clearsilver-0.3/cgi/cgiwrap.c	Mon Aug  6 14:28:16 2001
+++ clearsilver-0.4/cgi/cgiwrap.c	Fri Jan 11 15:42:08 2002
@@ -30,9 +30,10 @@
   ITERENV_FUNC iterenv_cb;
 
   void *data;
+  int emu_init;
 } CGIWRAPPER;
 
-static CGIWRAPPER GlobalWrapper;
+static CGIWRAPPER GlobalWrapper = {0, NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0};
 
 static void cgiwrap_init (void)
 {
@@ -51,6 +52,9 @@
 
 void cgiwrap_init_std (int argc, char **argv, char **envp)
 {
+  /* so you can compile the same code for embedded without mods */
+  if (GlobalWrapper.emu_init) return;
+
   cgiwrap_init();
   GlobalWrapper.argc = argc;
   GlobalWrapper.argv = argv;
@@ -70,6 +74,7 @@
   GlobalWrapper.getenv_cb = getenv_cb;
   GlobalWrapper.putenv_cb = putenv_cb;
   GlobalWrapper.iterenv_cb = iterenv_cb;
+  GlobalWrapper.emu_init = 1;
 }
 
 NEOERR *cgiwrap_getenv (char *k, char **v)
diff -Nbru clearsilver-0.3/cgi/html.c clearsilver-0.4/cgi/html.c
--- clearsilver-0.3/cgi/html.c	Fri Sep 14 18:23:03 2001
+++ clearsilver-0.4/cgi/html.c	Tue Nov  6 16:23:01 2001
@@ -131,11 +131,21 @@
     email_match.rm_so = -1;
     email_match.rm_eo = -1;
   }
+  else
+  {
+    email_match.rm_so += x;
+    email_match.rm_eo += x;
+  }
   if (regexec (&url_re, src+x, 1, &url_match, 0) != 0)
   {
     url_match.rm_so = -1;
     url_match.rm_eo = -1;
   }
+  else
+  {
+    url_match.rm_so += x;
+    url_match.rm_eo += x;
+  }
   while ((x < slen) && !((email_match.rm_so == -1) && (url_match.rm_so == -1)))
   {
     if (part >= part_count)
@@ -145,8 +155,8 @@
     }
     if ((url_match.rm_so != -1) && ((email_match.rm_so == -1) || (url_match.rm_so <= email_match.rm_so)))
     {
-      parts[part].begin = x + url_match.rm_so;
-      parts[part].end = x + url_match.rm_eo;
+      parts[part].begin = url_match.rm_so;
+      parts[part].end = url_match.rm_eo;
       parts[part].type = SC_TYPE_URL;
       x = parts[part].end + 1;
       part++;
@@ -157,6 +167,11 @@
 	  url_match.rm_so = -1;
 	  url_match.rm_eo = -1;
 	}
+	else
+	{
+	  url_match.rm_so += x;
+	  url_match.rm_eo += x;
+	}
 	if ((email_match.rm_so != -1) && (x > email_match.rm_so))
 	{
 	  if (regexec (&email_re, src+x, 1, &email_match, 0) != 0)
@@ -164,13 +179,18 @@
 	    email_match.rm_so = -1;
 	    email_match.rm_eo = -1;
 	  }
+	  else
+	  {
+	    email_match.rm_so += x;
+	    email_match.rm_eo += x;
+	  }
 	}
       }
     }
     else
     {
-      parts[part].begin = x + email_match.rm_so;
-      parts[part].end = x + email_match.rm_eo;
+      parts[part].begin = email_match.rm_so;
+      parts[part].end = email_match.rm_eo;
       parts[part].type = SC_TYPE_EMAIL;
       x = parts[part].end + 1;
       part++;
@@ -181,6 +201,11 @@
 	  email_match.rm_so = -1;
 	  email_match.rm_eo = -1;
 	}
+	else
+	{
+	  email_match.rm_so += x;
+	  email_match.rm_eo += x;
+	}
 	if ((url_match.rm_so != -1) && (x > url_match.rm_so))
 	{
 	  if (regexec (&url_re, src+x, 1, &url_match, 0) != 0)
@@ -188,6 +213,11 @@
 	    url_match.rm_so = -1;
 	    url_match.rm_eo = -1;
 	  }
+	  else
+	  {
+	    url_match.rm_so += x;
+	    url_match.rm_eo += x;
+	  }
 	}
       }
     }
@@ -276,9 +306,9 @@
 	      err = string_append (out, "&gt;");
 	    else if (src[x] == '\n')
 	      if (newlines) 
-		err = string_append (out, "<br>");
+		err = string_append (out, "<BR>");
 	      else if (x && src[x-1] == '\n')
-		err = string_append (out, "<p>");
+		err = string_append (out, "<P>");
 	      else 
 		err = string_append_char (out, '\n');
 	    else if (src[x] != '\r')
@@ -312,7 +342,7 @@
         char last_char = src[parts[i].end-1];
         int suffix=0;
         if (last_char == '.' || last_char == ',') { suffix=1; }
-	err = string_append (out, " <a target=_top href=\"");
+	err = string_append (out, " <A TARGET=\"_blank\" HREF=\"");
 	if (err != STATUS_OK) break;
 	if (!strncmp(src + x, "www.", 4))
 	{
@@ -325,7 +355,7 @@
 	if (err != STATUS_OK) break;
 	err = string_appendn (out, src + x, parts[i].end - x - suffix);
 	if (err != STATUS_OK) break;
-	err = string_append (out, "</a>");
+	err = string_append (out, "</A>");
         if (suffix) {
             err  = string_appendn(out,src + parts[i].end - 1,1);
 	    if (err != STATUS_OK) break;
@@ -333,7 +363,7 @@
       }
       else /* type == SC_TYPE_EMAIL */
       {
-	err = string_append (out, "<a href=\"mailto:");
+	err = string_append (out, "<A HREF=\"mailto:");
 	if (err != STATUS_OK) break;
 	err = string_appendn (out, src + x, parts[i].end - x);
 	if (err != STATUS_OK) break;
@@ -341,7 +371,7 @@
 	if (err != STATUS_OK) break;
 	err = string_appendn (out, src + x, parts[i].end - x);
 	if (err != STATUS_OK) break;
-	err = string_append (out, "</a>");
+	err = string_append (out, "</A>");
       }
       x = parts[i].end;
       i++;
diff -Nbru clearsilver-0.3/cgi/rfc2388.c clearsilver-0.4/cgi/rfc2388.c
--- clearsilver-0.3/cgi/rfc2388.c	Sun Sep  9 16:53:10 2001
+++ clearsilver-0.4/cgi/rfc2388.c	Tue Oct 16 15:39:59 2001
@@ -392,12 +392,12 @@
       if (filename)
       {
 	if (last) fwrite (last, sizeof(char), strlen(last), fp);
-	if (l > 2 && s[l-1] == '\n' && s[l-2] == '\r')
+	if (l > 1 && s[l-1] == '\n' && s[l-2] == '\r')
 	{
 	  last = "\r\n";
 	  l-=2;
 	}
-	else if (l > 1 && s[l-1] == '\n')
+	else if (l > 0 && s[l-1] == '\n')
 	{
 	  last = "\n";
 	  l--;
diff -Nbru clearsilver-0.3/cgi/static.c clearsilver-0.4/cgi/static.c
--- clearsilver-0.3/cgi/static.c	Mon Aug  6 14:28:16 2001
+++ clearsilver-0.4/cgi/static.c	Wed Oct 10 17:26:13 2001
@@ -47,6 +47,7 @@
   {
     *p = '\0';
     err = hdf_set_value (cgi->hdf, "hdf.loadpaths.0", cs_file);
+    chdir(cs_file);
     *p = '/';
     if (err)
     {
diff -Nbru clearsilver-0.3/cs/Makefile clearsilver-0.4/cs/Makefile
--- clearsilver-0.3/cs/Makefile	Sat Sep 15 17:08:55 2001
+++ clearsilver-0.4/cs/Makefile	Tue Nov 13 13:58:15 2001
@@ -23,7 +23,7 @@
 
 TARGETS = $(CS_LIB) $(CSTEST_EXE) test
 
-CS_TESTS = test.cs test2.cs test3.cs test4.cs test5.cs test6.cs
+CS_TESTS = test.cs test2.cs test3.cs test4.cs test5.cs test6.cs test7.cs
 
 all: $(TARGETS)
 
@@ -43,7 +43,7 @@
 		./cstest test.hdf $$test > $$test.gold; \
 	done
 
-test: $(CSTEST_EXE)
+test: $(CSTEST_EXE) $(CS_TESTS)
 	@echo "Running cs regression tests"
 	@for test in $(CS_TESTS); do \
 		rm -f $$test.out; \
diff -Nbru clearsilver-0.3/cs/cs.h clearsilver-0.4/cs/cs.h
--- clearsilver-0.3/cs/cs.h	Sat Sep 15 16:38:40 2001
+++ clearsilver-0.4/cs/cs.h	Tue Nov 13 13:58:15 2001
@@ -15,13 +15,15 @@
  * CS_OPEN     := <?cs
  * CS_CLOSE    := ?>
  * COMMAND     := (CMD_IF | CMD_VAR | CMD_EVAR | CMD_INCLUDE | CMD_EACH
- *                 | CMD_DEF | CMD_CALL | CMD_SET )
+ *                 | CMD_DEF | CMD_CALL | CMD_SET | CMD_LOOP )
  * CMD_IF      := CS_OPEN IF CS_CLOSE CS CMD_ENDIF
  * CMD_ENDIF   := CS_OPEN ENDIF CS_CLOSE
  * CMD_INCLUDE := CS_OPEN INCLUDE CS_CLOSE
  * CMD_DEF     := CS_OPEN DEF CS_CLOSE
  * CMD_CALL    := CS_OPEN CALL CS_CLOSE
  * CMD_SET     := CS_OPEN SET CS_CLOSE
+ * CMD_LOOP    := CS_OPEN LOOP CS_CLOSE
+ * LOOP        := loop:VAR = EXPR, EXPR, EXPR
  * SET         := set:VAR = EXPR
  * EXPR        := (ARG | ARG OP EXPR)
  * CALL        := call:VAR LPAREN ARG (,ARG)* RPAREN
diff -Nbru clearsilver-0.3/cs/csparse.c clearsilver-0.4/cs/csparse.c
--- clearsilver-0.3/cs/csparse.c	Sat Sep 15 17:22:56 2001
+++ clearsilver-0.4/cs/csparse.c	Thu Jan 10 15:11:39 2002
@@ -34,9 +34,10 @@
   ST_EACH = 1<<3,
   ST_POP = 1<<4,
   ST_DEF = 1<<5,
+  ST_LOOP =  1<<6,
 } CS_STATE;
 
-#define ST_ANYWHERE (ST_EACH | ST_ELSE | ST_IF | ST_GLOBAL | ST_DEF)
+#define ST_ANYWHERE (ST_EACH | ST_ELSE | ST_IF | ST_GLOBAL | ST_DEF | ST_LOOP)
 
 typedef struct _stack_entry 
 {
@@ -70,6 +71,9 @@
 static NEOERR *call_eval (CSPARSE *parse, CSTREE *node, CSTREE **next);
 static NEOERR *set_parse (CSPARSE *parse, int cmd, char *arg);
 static NEOERR *set_eval (CSPARSE *parse, CSTREE *node, CSTREE **next);
+static NEOERR *loop_parse (CSPARSE *parse, int cmd, char *arg);
+static NEOERR *loop_eval (CSPARSE *parse, CSTREE *node, CSTREE **next);
+static NEOERR *endloop_parse (CSPARSE *parse, int cmd, char *arg);
 
 static NEOERR *render_node (CSPARSE *parse, CSTREE *node);
 
@@ -109,7 +113,7 @@
     endeach_parse, skip_eval, 0},
   {"include", sizeof("include")-1, ST_ANYWHERE,     ST_SAME, 
     include_parse, skip_eval, 1},
-  {"def",     sizeof("def")-1,     ST_GLOBAL,       ST_DEF, 
+  {"def",     sizeof("def")-1,     ST_ANYWHERE,     ST_DEF, 
     def_parse, skip_eval, 1},
   {"/def",    sizeof("/def")-1,    ST_DEF,          ST_POP,
     enddef_parse, skip_eval, 0},
@@ -117,6 +121,10 @@
     call_parse, call_eval, 1},
   {"set",    sizeof("set")-1,    ST_ANYWHERE,     ST_SAME,
     set_parse, set_eval, 1},
+  {"loop",    sizeof("loop")-1,    ST_ANYWHERE,     ST_LOOP,
+    loop_parse, loop_eval, 1},
+  {"/loop",    sizeof("/loop")-1,    ST_LOOP,     ST_POP,
+    endloop_parse, skip_eval, 1},
   {NULL},
 };
 
@@ -435,7 +443,7 @@
 	  if (!strncasecmp(token, Commands[i].cmd, n))
 	  {
 	    if ((Commands[i].has_arg && ((token[n] == ':') || (token[n] == '!')))
-		|| (token[n] == ' ' || token[n] == '\0'))
+		|| (token[n] == ' ' || token[n] == '\0' || token[n] == '\r' || token[n] == '\n'))
 	    {
 	      err = uListGet (parse->stack, -1, (void **)&entry);
 	      if (err != STATUS_OK) goto cs_parse_done;
@@ -639,193 +647,6 @@
     return atoi(vs);
 }
 
-static NEOERR *literal_parse (CSPARSE *parse, int cmd, char *arg)
-{
-  NEOERR *err;
-  CSTREE *node;
-
-  /* ne_warn ("literal: %s", arg); */
-  err = alloc_node (&node);
-  if (err) return nerr_pass(err);
-  node->cmd = cmd;
-  node->arg1.op_type = CS_TYPE_STRING;
-  node->arg1.s = arg;
-  *(parse->next) = node;
-  parse->next = &(node->next);
-  parse->current = node;
-
-  return STATUS_OK;
-}
-
-static NEOERR *literal_eval (CSPARSE *parse, CSTREE *node, CSTREE **next)
-{
-  NEOERR *err = STATUS_OK;
-
-  if (node->arg1.s != NULL)
-    err = parse->output_cb (parse->output_ctx, node->arg1.s);
-  *next = node->next;
-  return nerr_pass(err);
-}
-
-static NEOERR *name_parse (CSPARSE *parse, int cmd, char *arg)
-{
-  NEOERR *err;
-  CSTREE *node;
-  char *a, *s;
-  char tmp[256];
-
-  /* ne_warn ("name: %s", arg); */
-  err = alloc_node (&node);
-  if (err) return nerr_pass(err);
-  node->cmd = cmd;
-  if (arg[0] == '!')
-    node->flags |= CSF_REQUIRED;
-  arg++;
-  /* Validate arg is a var (regex /^[#" ]$/) */
-  a = neos_strip(arg);
-  s = strpbrk(a, "#\" <>");
-  if (s != NULL)
-  {
-    dealloc_node(&node);
-    return nerr_raise (NERR_PARSE, "%s Invalid character in var name %s: %c", 
-	find_context(parse, -1, tmp, sizeof(tmp)),
-	a, s[0]);
-  }
-
-  node->arg1.op_type = CS_TYPE_VAR;
-  node->arg1.s = a;
-  *(parse->next) = node;
-  parse->next = &(node->next);
-  parse->current = node;
-  
-  return STATUS_OK;
-}
-
-static NEOERR *name_eval (CSPARSE *parse, CSTREE *node, CSTREE **next)
-{
-  NEOERR *err = STATUS_OK;
-  HDF *obj;
-  char *v;
-
-  if (node->arg1.op_type == CS_TYPE_VAR && node->arg1.s != NULL)
-  {
-    obj = var_lookup_obj (parse, node->arg1.s);
-    if (obj != NULL)
-    {
-      v = hdf_obj_name(obj);
-      err = parse->output_cb (parse->output_ctx, v);
-    }
-  }
-  *next = node->next;
-  return nerr_pass(err);
-}
-
-static NEOERR *var_parse (CSPARSE *parse, int cmd, char *arg)
-{
-  NEOERR *err;
-  CSTREE *node;
-  char *a, *s;
-  char tmp[256];
-
-  /* ne_warn ("var: %s", arg); */
-  err = alloc_node (&node);
-  if (err) return nerr_pass(err);
-  node->cmd = cmd;
-  if (arg[0] == '!')
-    node->flags |= CSF_REQUIRED;
-  arg++;
-  /* Validate arg is a var (regex /^[#" ]$/) */
-  a = neos_strip(arg);
-  s = strpbrk(a, "#\" <>");
-  if (s != NULL)
-  {
-    dealloc_node(&node);
-    return nerr_raise (NERR_PARSE, "%s Invalid character in var name %s: %c", 
-	find_context(parse, -1, tmp, sizeof(tmp)),
-	a, s[0]);
-  }
-
-  node->arg1.op_type = CS_TYPE_VAR;
-  node->arg1.s = a;
-  *(parse->next) = node;
-  parse->next = &(node->next);
-  parse->current = node;
-  
-  return STATUS_OK;
-}
-
-static NEOERR *var_eval (CSPARSE *parse, CSTREE *node, CSTREE **next)
-{
-  NEOERR *err = STATUS_OK;
-  char *v;
-
-  if (node->arg1.op_type == CS_TYPE_VAR && node->arg1.s != NULL)
-  {
-    v = var_lookup (parse, node->arg1.s);
-    if (v != NULL)
-      err = parse->output_cb (parse->output_ctx, v);
-  }
-  *next = node->next;
-  return nerr_pass(err);
-}
-
-static NEOERR *evar_parse (CSPARSE *parse, int cmd, char *arg)
-{
-  NEOERR *err;
-  CSTREE *node;
-  char *a, *s;
-  char *save_context;
-  int save_infile;
-  char tmp[256];
-
-  /* ne_warn ("evar: %s", arg); */
-  err = alloc_node (&node);
-  if (err) return nerr_pass(err);
-  node->cmd = cmd;
-  if (arg[0] == '!')
-    node->flags |= CSF_REQUIRED;
-  arg++;
-  /* Validate arg is a var (regex /^[#" ]$/) */
-  a = neos_strip(arg);
-  s = strpbrk(a, "#\" <>");
-  if (s != NULL)
-  {
-    dealloc_node(&node);
-    return nerr_raise (NERR_PARSE, "%s Invalid character in var name %s: %c", 
-	find_context(parse, -1, tmp, sizeof(tmp)),
-	a, s[0]);
-  }
-
-  err = hdf_get_copy (parse->hdf, a, &s, NULL);
-  if (err) 
-  {
-    dealloc_node(&node);
-    return nerr_pass (err);
-  }
-  if (node->flags & CSF_REQUIRED && s == NULL) 
-  {
-    dealloc_node(&node);
-    return nerr_raise (NERR_NOT_FOUND, "%s Unable to evar empty variable %s",
-	find_context(parse, -1, tmp, sizeof(tmp)), a);
-  }
-
-  node->arg1.op_type = CS_TYPE_VAR;
-  node->arg1.s = a;
-  *(parse->next) = node;
-  parse->next = &(node->next);
-  parse->current = node;
-
-  save_context = parse->context;
-  save_infile = parse->in_file;
-  parse->context = a;
-  parse->in_file = 0;
-  if (s) err = cs_parse_string (parse, s, strlen(s));
-  parse->context = save_context;
-  parse->in_file = save_infile;
-
-  return nerr_pass (err);
-}
-
 typedef struct _token
 {
   CSTOKEN_TYPE type;
@@ -987,52 +808,217 @@
     }
     else
     {
-      return nerr_raise (NERR_PARSE, 
-	  "%s Terminal token is not an argument, type is %d",
-	  find_context(parse, -1, tmp, sizeof(tmp)), tokens[0].type);
-    }
+      return nerr_raise (NERR_PARSE, 
+	  "%s Terminal token is not an argument, type is %d",
+	  find_context(parse, -1, tmp, sizeof(tmp)), tokens[0].type);
+    }
+  }
+
+  while (BinaryOpOrder[op])
+  {
+    x = ntokens-1;
+    while (x >= 0)
+    {
+      if (tokens[x].type & BinaryOpOrder[op])
+      {
+	arg->op_type = tokens[x].type;
+	arg->expr2 = (CSARG *) calloc (1, sizeof (CSARG));
+	arg->expr1 = (CSARG *) calloc (1, sizeof (CSARG));
+	if (arg->expr1 == NULL || arg->expr2 == NULL)
+	  return nerr_raise (NERR_NOMEM, 
+	      "%s Unable to allocate memory for expression", 
+	      find_context(parse, -1, tmp, sizeof(tmp)));
+	err = parse_expr2(parse, tokens + x + 1, ntokens-x-1, arg->expr2);
+	if (err) return nerr_pass (err);
+	err = parse_expr2(parse, tokens, x, arg->expr1);
+	if (err) return nerr_pass (err);
+	return STATUS_OK;
+      }
+      x--;
+    }
+    op++;
+  }
+  return nerr_raise (NERR_PARSE, "%s Bad Expression",
+      find_context(parse, -1, tmp, sizeof(tmp)));
+}
+
+static NEOERR *parse_expr (CSPARSE *parse, char *arg, CSARG *expr)
+{
+  NEOERR *err;
+  CSTOKEN tokens[MAX_TOKENS];
+  int ntokens = 0;
+
+  memset(tokens, 0, sizeof(CSTOKEN) * MAX_TOKENS);
+  err = parse_tokens (parse, arg, tokens, &ntokens);
+  if (err) return nerr_pass(err);
+  err = parse_expr2 (parse, tokens, ntokens, expr);
+  if (err) return nerr_pass(err);
+  return STATUS_OK;
+}
+
+static NEOERR *literal_parse (CSPARSE *parse, int cmd, char *arg)
+{
+  NEOERR *err;
+  CSTREE *node;
+
+  /* ne_warn ("literal: %s", arg); */
+  err = alloc_node (&node);
+  if (err) return nerr_pass(err);
+  node->cmd = cmd;
+  node->arg1.op_type = CS_TYPE_STRING;
+  node->arg1.s = arg;
+  *(parse->next) = node;
+  parse->next = &(node->next);
+  parse->current = node;
+
+  return STATUS_OK;
+}
+
+static NEOERR *literal_eval (CSPARSE *parse, CSTREE *node, CSTREE **next)
+{
+  NEOERR *err = STATUS_OK;
+
+  if (node->arg1.s != NULL)
+    err = parse->output_cb (parse->output_ctx, node->arg1.s);
+  *next = node->next;
+  return nerr_pass(err);
+}
+
+static NEOERR *name_parse (CSPARSE *parse, int cmd, char *arg)
+{
+  NEOERR *err;
+  CSTREE *node;
+  char *a, *s;
+  char tmp[256];
+
+  /* ne_warn ("name: %s", arg); */
+  err = alloc_node (&node);
+  if (err) return nerr_pass(err);
+  node->cmd = cmd;
+  if (arg[0] == '!')
+    node->flags |= CSF_REQUIRED;
+  arg++;
+  /* Validate arg is a var (regex /^[#" ]$/) */
+  a = neos_strip(arg);
+  s = strpbrk(a, "#\" <>");
+  if (s != NULL)
+  {
+    dealloc_node(&node);
+    return nerr_raise (NERR_PARSE, "%s Invalid character in var name %s: %c", 
+	find_context(parse, -1, tmp, sizeof(tmp)),
+	a, s[0]);
+  }
+
+  node->arg1.op_type = CS_TYPE_VAR;
+  node->arg1.s = a;
+  *(parse->next) = node;
+  parse->next = &(node->next);
+  parse->current = node;
+  
+  return STATUS_OK;
+}
+
+static NEOERR *name_eval (CSPARSE *parse, CSTREE *node, CSTREE **next)
+{
+  NEOERR *err = STATUS_OK;
+  HDF *obj;
+  char *v;
+
+  if (node->arg1.op_type == CS_TYPE_VAR && node->arg1.s != NULL)
+  {
+    obj = var_lookup_obj (parse, node->arg1.s);
+    if (obj != NULL)
+    {
+      v = hdf_obj_name(obj);
+      err = parse->output_cb (parse->output_ctx, v);
+    }
+  }
+  *next = node->next;
+  return nerr_pass(err);
+}
+
+static NEOERR *var_parse (CSPARSE *parse, int cmd, char *arg)
+{
+  NEOERR *err;
+  CSTREE *node;
+
+  /* ne_warn ("var: %s", arg); */
+  err = alloc_node (&node);
+  if (err) return nerr_pass(err);
+  node->cmd = cmd;
+  if (arg[0] == '!')
+    node->flags |= CSF_REQUIRED;
+  arg++;
+  /* Validate arg is a var (regex /^[#" ]$/) */
+  err = parse_expr (parse, arg, &(node->arg1));
+  if (err)
+  {
+    dealloc_node(&node);
+    return nerr_pass(err);
+  }
+
+  *(parse->next) = node;
+  parse->next = &(node->next);
+  parse->current = node;
+  
+  return STATUS_OK;
+}
+
+static NEOERR *evar_parse (CSPARSE *parse, int cmd, char *arg)
+{
+  NEOERR *err;
+  CSTREE *node;
+  char *a, *s;
+  char *save_context;
+  int save_infile;
+  char tmp[256];
+
+  /* ne_warn ("evar: %s", arg); */
+  err = alloc_node (&node);
+  if (err) return nerr_pass(err);
+  node->cmd = cmd;
+  if (arg[0] == '!')
+    node->flags |= CSF_REQUIRED;
+  arg++;
+  /* Validate arg is a var (regex /^[#" ]$/) */
+  a = neos_strip(arg);
+  s = strpbrk(a, "#\" <>");
+  if (s != NULL)
+  {
+    dealloc_node(&node);
+    return nerr_raise (NERR_PARSE, "%s Invalid character in var name %s: %c", 
+	find_context(parse, -1, tmp, sizeof(tmp)),
+	a, s[0]);
   }
 
-  while (BinaryOpOrder[op])
-  {
-    x = ntokens-1;
-    while (x >= 0)
-    {
-      if (tokens[x].type & BinaryOpOrder[op])
+  err = hdf_get_copy (parse->hdf, a, &s, NULL);
+  if (err) 
       {
-	arg->op_type = tokens[x].type;
-	arg->expr2 = (CSARG *) calloc (1, sizeof (CSARG));
-	arg->expr1 = (CSARG *) calloc (1, sizeof (CSARG));
-	if (arg->expr1 == NULL || arg->expr2 == NULL)
-	  return nerr_raise (NERR_NOMEM, 
-	      "%s Unable to allocate memory for expression", 
-	      find_context(parse, -1, tmp, sizeof(tmp)));
-	err = parse_expr2(parse, tokens + x + 1, ntokens-x-1, arg->expr2);
-	if (err) return nerr_pass (err);
-	err = parse_expr2(parse, tokens, x, arg->expr1);
-	if (err) return nerr_pass (err);
-	return STATUS_OK;
-      }
-      x--;
+    dealloc_node(&node);
+    return nerr_pass (err);
     }
-    op++;
+  if (node->flags & CSF_REQUIRED && s == NULL) 
+  {
+    dealloc_node(&node);
+    return nerr_raise (NERR_NOT_FOUND, "%s Unable to evar empty variable %s",
+	find_context(parse, -1, tmp, sizeof(tmp)), a);
   }
-  return nerr_raise (NERR_PARSE, "%s Bad Expression",
-      find_context(parse, -1, tmp, sizeof(tmp)));
-}
 
-static NEOERR *parse_expr (CSPARSE *parse, char *arg, CSARG *expr)
-{
-  NEOERR *err;
-  CSTOKEN tokens[MAX_TOKENS];
-  int ntokens = 0;
+  node->arg1.op_type = CS_TYPE_VAR;
+  node->arg1.s = a;
+  *(parse->next) = node;
+  parse->next = &(node->next);
+  parse->current = node;
 
-  memset(tokens, 0, sizeof(CSTOKEN) * MAX_TOKENS);
-  err = parse_tokens (parse, arg, tokens, &ntokens);
-  if (err) return nerr_pass(err);
-  err = parse_expr2 (parse, tokens, ntokens, expr);
-  if (err) return nerr_pass(err);
-  return STATUS_OK;
+  save_context = parse->context;
+  save_infile = parse->in_file;
+  parse->context = a;
+  parse->in_file = 0;
+  if (s) err = cs_parse_string (parse, s, strlen(s));
+  parse->context = save_context;
+  parse->in_file = save_infile;
+
+  return nerr_pass (err);
 }
 
 static NEOERR *if_parse (CSPARSE *parse, int cmd, char *arg)
@@ -1117,9 +1103,13 @@
 
 static NEOERR *eval_expr (CSPARSE *parse, CSARG *expr, CSARG *result)
 {
+  memset(result, 0, sizeof(CSARG));
   if (expr->op_type & CS_TYPES)
   {
     *result = *expr;
+    /* we transfer ownership of the string here.. ugh */
+    if (expr->op_type == CS_TYPE_STRING_ALLOC)
+      expr->op_type = CS_TYPE_STRING;
     return STATUS_OK;
   }
   else
@@ -1223,11 +1213,18 @@
 	    result->n = (s2 == NULL) ? 1 : 0;
 	    break;
 	  case CS_OP_ADD:
-	    result->op_type = CS_TYPE_STRING;
 	    if (s1 == NULL) 
+	    {
 	      result->s = s2;
+	      result->op_type = arg2.op_type;
+	      arg2.op_type = CS_TYPE_STRING;
+	    }
 	    else
+	    {
 	      result->s = s1;
+	      result->op_type = arg1.op_type;
+	      arg1.op_type = CS_TYPE_STRING;
+	    }
 	    break;
 	  default:
 	    ne_warn ("Unsupported op %d in eval_expr", expr->op_type);
@@ -1262,7 +1259,7 @@
 	    result->s = (char *) calloc ((strlen(s1) + strlen(s2) + 1), sizeof(char));
 	    if (result->s == NULL)
 	      return nerr_raise (NERR_NOMEM, "Unable to allocate memory to concatenate strings in expression: %s + %s", s1, s2);
-	    strcat(result->s, s1);
+	    strcpy(result->s, s1);
 	    strcat(result->s, s2);
 	    break;
 	  default:
@@ -1271,6 +1268,7 @@
 	}
       }
     }
+    
     if (arg1.op_type == CS_TYPE_STRING_ALLOC)
       free(arg1.s);
     if (arg2.op_type == CS_TYPE_STRING_ALLOC)
@@ -1279,6 +1277,39 @@
   return STATUS_OK;
 }
 
+static NEOERR *var_eval (CSPARSE *parse, CSTREE *node, CSTREE **next)
+{
+  NEOERR *err = STATUS_OK;
+  CSARG val;
+
+  err = eval_expr(parse, &(node->arg1), &val);
+  if (err) return nerr_pass(err);
+  if (val.op_type & (CS_TYPE_NUM | CS_TYPE_VAR_NUM))
+  { 
+    char buf[256];
+    long int n_val;
+
+    n_val = arg_eval_num (parse, &val);
+    snprintf (buf, sizeof(buf), "%ld", n_val);
+    err = parse->output_cb (parse->output_ctx, buf);
+  }
+  else
+  {
+    char *s = arg_eval (parse, &val);
+    /* Do we set it to blank if s == NULL? */
+    if (s)
+    {
+      err = parse->output_cb (parse->output_ctx, s);
+    }
+  }
+  if (val.op_type == CS_TYPE_STRING_ALLOC)
+    free(val.s);
+
+  *next = node->next;
+  return nerr_pass(err);
+}
+
+
 static NEOERR *if_eval (CSPARSE *parse, CSTREE *node, CSTREE **next)
 {
   NEOERR *err = STATUS_OK;
@@ -1926,6 +1957,194 @@
 
   *next = node->next;
   return nerr_pass (err);
+}
+
+static NEOERR *loop_parse (CSPARSE *parse, int cmd, char *arg)
+{
+  NEOERR *err;
+  CSTREE *node;
+  CSARG *carg, *larg = NULL;
+  BOOL last = FALSE;
+  char *lvar;
+  char *p, *a;
+  char tmp[256];
+  int x;
+
+  err = alloc_node (&node);
+  if (err) return nerr_pass(err);
+  node->cmd = cmd;
+  if (arg[0] == '!')
+    node->flags |= CSF_REQUIRED;
+  arg++;
+
+  p = lvar = neos_strip(arg);
+  while (*p && !isspace(*p) && *p != '=') p++;
+  if (*p == '\0')
+  {
+    dealloc_node(&node);
+    return nerr_raise (NERR_PARSE, 
+	"%s Improperly formatted loop directive: %s", 
+	find_context(parse, -1, tmp, sizeof(tmp)), arg);
+  }
+  if (*p != '=')
+  {
+    *p++ = '\0';
+    while (*p && *p != '=') p++;
+    if (*p == '\0')
+    {
+      dealloc_node(&node);
+      return nerr_raise (NERR_PARSE, 
+	  "%s Improperly formatted loop directive: %s", 
+	  find_context(parse, -1, tmp, sizeof(tmp)), arg);
+    }
+    p++;
+  }
+  else
+  {
+    *p++ = '\0';
+  }
+  while (*p && isspace(*p)) p++;
+  if (*p == '\0')
+  {
+    dealloc_node(&node);
+    return nerr_raise (NERR_PARSE, 
+	"%s Improperly formatted loop directive: %s", 
+	find_context(parse, -1, tmp, sizeof(tmp)), arg);
+  }
+  node->arg1.op_type = CS_TYPE_VAR;
+  node->arg1.s = lvar;
+
+  x = 0;
+  while (*p)
+  {
+    carg = (CSARG *) calloc (1, sizeof(CSARG));
+    if (carg == NULL)
+    {
+      err = nerr_raise (NERR_NOMEM, 
+	  "%s Unable to allocate memory for CSARG in loop %s",
+	  find_context(parse, -1, tmp, sizeof(tmp)), arg);
+      break;
+    }
+    if (larg == NULL)
+    {
+      node->vargs = carg;
+      larg = carg;
+    }
+    else
+    {
+      larg->next = carg;
+      larg = carg;
+    }
+    x++;
+    a = strpbrk(p, ",");
+    if (a == NULL) last = TRUE;
+    else *a = '\0';
+    err = parse_expr (parse, p, carg);
+    if (err) break;
+    if (last == TRUE) break;
+    p = a+1;
+  }
+  if (!err && ((x < 1) || (x > 3)))
+  {
+    err = nerr_raise (NERR_PARSE, 
+	"%s Incorrect number of arguments, expected 1, 2, or 3 got %d in loop: %s",
+	find_context(parse, -1, tmp, sizeof(tmp)), x, arg);
+  }
+
+  /* ne_warn ("loop %s %s", lvar, p); */
+
+  *(parse->next) = node;
+  parse->next = &(node->case_0);
+  parse->current = node;
+
+  return STATUS_OK;
+}
+
+static NEOERR *loop_eval (CSPARSE *parse, CSTREE *node, CSTREE **next)
+{
+  NEOERR *err = STATUS_OK;
+  CS_LOCAL_MAP each_map;
+  int var;
+  int start = 0, end = 0, step = 1;
+  int x, iter = 1;
+  CSARG *carg;
+  CSARG val;
+
+  carg = node->vargs;
+  if (carg == NULL) return nerr_raise (NERR_ASSERT, "No arguments in loop eval?");
+  err = eval_expr(parse, carg, &val);
+  if (err) return nerr_pass(err);
+  end = arg_eval_num(parse, &val);
+  if (val.op_type == CS_TYPE_STRING_ALLOC) free(val.s);
+  if (carg->next)
+  {
+    start = end;
+    carg = carg->next;
+    err = eval_expr(parse, carg, &val);
+    if (err) return nerr_pass(err);
+    end = arg_eval_num(parse, &val);
+    if (val.op_type == CS_TYPE_STRING_ALLOC) free(val.s);
+    if (carg->next)
+    {
+      carg = carg->next;
+      err = eval_expr(parse, carg, &val);
+      if (err) return nerr_pass(err);
+      step = arg_eval_num(parse, &val);
+      if (val.op_type == CS_TYPE_STRING_ALLOC) free(val.s);
+    }
+  }
+  /* automatically handle cases where the step is backwards */
+  if (((step < 0) && (start < end)) || 
+      ((step > 0) && (end < start)))
+  {
+    x = start;
+    start = end;
+    end = x;
+  }
+  if (step == 0)
+  {
+    if (start == end) iter = 1;
+    else iter = 0;
+  }
+  else 
+  {
+    iter = abs((end - start) / step + 1);
+  }
+
+  if (iter > 0)
+  {
+    /* Init and install local map */
+    each_map.type = CS_TYPE_NUM;
+    each_map.name = node->arg1.s;
+    each_map.next = parse->locals;
+    parse->locals = &each_map;
+
+    var = start;
+    for (x = 0, var = start; x < iter; x++, var += step)
+    {
+      each_map.value.n = var;
+      err = render_node (parse, node->case_0);
+      if (err != STATUS_OK) break;
+    } 
+
+    /* Remove local map */
+    parse->locals = each_map.next;
+  }
+
+  *next = node->next;
+  return nerr_pass (err);
+}
+static NEOERR *endloop_parse (CSPARSE *parse, int cmd, char *arg)
+{
+  NEOERR *err;
+  STACK_ENTRY *entry;
+
+  err = uListGet (parse->stack, -1, (void **)&entry);
+  if (err != STATUS_OK) return nerr_pass(err);
+
+  parse->next = &(entry->tree->next);
+  parse->current = entry->tree;
+  return STATUS_OK;
 }
 
 static NEOERR *skip_eval (CSPARSE *parse, CSTREE *node, CSTREE **next)
diff -Nbru clearsilver-0.3/cs/test7.cs clearsilver-0.4/cs/test7.cs
--- clearsilver-0.3/cs/test7.cs	Wed Dec 31 16:00:00 1969
+++ clearsilver-0.4/cs/test7.cs	Tue Nov 13 13:58:15 2001
@@ -0,0 +1,14 @@
+
+LOOP Test
+
+1, 3, 5
+<?cs loop:x = #1, #5, #2 ?><?cs var:x ?>, <?cs /loop ?>
+
+1, 3, 5... 205
+<?cs loop:x = #1, #205, #2 ?><?cs var:x ?>, <?cs /loop ?>
+
+backwards
+<?cs loop:x = #205, #1, "-2" ?><?cs var:x ?>, <?cs /loop ?>
+
+broken
+<?cs loop:x = #1, #205, #-2 ?><?cs var:x ?>, <?cs /loop ?>
diff -Nbru clearsilver-0.3/cs/test7.cs.gold clearsilver-0.4/cs/test7.cs.gold
--- clearsilver-0.3/cs/test7.cs.gold	Wed Dec 31 16:00:00 1969
+++ clearsilver-0.4/cs/test7.cs.gold	Tue Nov 13 13:58:15 2001
@@ -0,0 +1,15 @@
+Parsing test7.cs
+
+LOOP Test
+
+1, 3, 5
+1, 3, 5, 
+
+1, 3, 5... 205
+1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25, 27, 29, 31, 33, 35, 37, 39, 41, 43, 45, 47, 49, 51, 53, 55, 57, 59, 61, 63, 65, 67, 69, 71, 73, 75, 77, 79, 81, 83, 85, 87, 89, 91, 93, 95, 97, 99, 101, 103, 105, 107, 109, 111, 113, 115, 117, 119, 121, 123, 125, 127, 129, 131, 133, 135, 137, 139, 141, 143, 145, 147, 149, 151, 153, 155, 157, 159, 161, 163, 165, 167, 169, 171, 173, 175, 177, 179, 181, 183, 185, 187, 189, 191, 193, 195, 197, 199, 201, 203, 205, 
+
+backwards
+205, 203, 201, 199, 197, 195, 193, 191, 189, 187, 185, 183, 181, 179, 177, 175, 173, 171, 169, 167, 165, 163, 161, 159, 157, 155, 153, 151, 149, 147, 145, 143, 141, 139, 137, 135, 133, 131, 129, 127, 125, 123, 121, 119, 117, 115, 113, 111, 109, 107, 105, 103, 101, 99, 97, 95, 93, 91, 89, 87, 85, 83, 81, 79, 77, 75, 73, 71, 69, 67, 65, 63, 61, 59, 57, 55, 53, 51, 49, 47, 45, 43, 41, 39, 37, 35, 33, 31, 29, 27, 25, 23, 21, 19, 17, 15, 13, 11, 9, 7, 5, 3, 1, 
+
+broken
+205, 203, 201, 199, 197, 195, 193, 191, 189, 187, 185, 183, 181, 179, 177, 175, 173, 171, 169, 167, 165, 163, 161, 159, 157, 155, 153, 151, 149, 147, 145, 143, 141, 139, 137, 135, 133, 131, 129, 127, 125, 123, 121, 119, 117, 115, 113, 111, 109, 107, 105, 103, 101, 99, 97, 95, 93, 91, 89, 87, 85, 83, 81, 79, 77, 75, 73, 71, 69, 67, 65, 63, 61, 59, 57, 55, 53, 51, 49, 47, 45, 43, 41, 39, 37, 35, 33, 31, 29, 27, 25, 23, 21, 19, 17, 15, 13, 11, 9, 7, 5, 3, 1, 
diff -Nbru clearsilver-0.3/mod_ecs/Makefile.tmpl clearsilver-0.4/mod_ecs/Makefile.tmpl
--- clearsilver-0.3/mod_ecs/Makefile.tmpl	Wed Dec 31 16:00:00 1969
+++ clearsilver-0.4/mod_ecs/Makefile.tmpl	Fri Jan 11 15:32:32 2002
@@ -0,0 +1 @@
+# Makefile for mod_ecs
diff -Nbru clearsilver-0.3/mod_ecs/README clearsilver-0.4/mod_ecs/README
--- clearsilver-0.3/mod_ecs/README	Wed Dec 31 16:00:00 1969
+++ clearsilver-0.4/mod_ecs/README	Fri Jan 11 15:42:27 2002
@@ -0,0 +1,83 @@
+mod_ecs - Apache Embedded ClearSilver CGI Module
+-------------------------------------------------------
+mod_ecs is based on a heavily modified version of mod_ecgi from:
+http://www.webthing.com/software/mod_ecgi.html
+
+This directory contains an Apache module which is designed to work with
+the ClearSilver CGI Kit.  The point of this Apache module is
+performance, if your server is under sufficient load that the overhead
+of forking and execing the CGI for every request is too much, this
+module is for you.  This module is also useful if you want some of the
+benefits of having a long-lived program: ie, the CGI can maintain
+connections to data sources such as databases or cache data in memory.
+The chief disadvantage is the same thing: your CGI becomes a long lived
+process, and you have to watch that you don't hold connections or memory
+that you don't want to.  You might want to look into the Apache
+configuration directives for limiting the number of connections that
+each child process handles: MaxRequestsPerChild.
+
+If you are already using the full ClearSilver CGI Kit, all you need to
+do to compile for the embedded ClearSilver is compile to a shared
+library instead of to an executable.  For instance, under Linux:
+
+ Executable: ld -o static.cgi -lneo_cgi -lneo_cs -lneo_util
+ Shared Library: ld -shared -fPic -o static.cso -lneo_cgi -lneo_cs -lneo_util
+
+Also, remember not to call exit(), as this will cause the entire Apache
+child to exit.
+
+There are two extra functions you can have in your CGI that the embedded
+ClearSilver module will try to find and call if they exist.  They are:
+void ECSInit(void);
+and
+void ECSCleanup(void);
+
+The first is called when the embedded CGI is loaded, the second before
+the embedded CGI is unloaded.
+
+This module supports the following three Apache configuration
+directives:
+ECSReload <yes/no>
+  When yes, mod_ecs will stat the .cso every time its run, to see if the 
+  file on disk has been updated.  If it has been updated, it will reload
+  the shared file from disk.  This incurs some performance overhead, and
+  when a change does occur, it removes most of the gains of ECSPreload.
+  Notice also that on many operating systems, changing a shared library
+  on disk that has been demand loaded can cause problems such as unexpected
+  core dumps.  This setting is most useful for development environments where
+  speed/throughput requirements aren't as high, but constant change is a 
+  factor.
+ECSDepLib <path>
+  Brings the benefits of ECSPreload to dependent shared libraries.  Will
+  cause mod_ecs to dlopen() the library at apache initialization time.
+  This can be avoided by using ECSPreload and having an ECSInit()
+  function in your library which does the shared library initialization.
+ECSPreload <path>
+  This function can be used to preload any shared libraries that you might
+  be calling later.  This allows you to hide the latency of load/init time
+  from your users by doing it once at apache initialization.
+
+Requirements:
+  A later version of Apache 1.3.x, probably 1.3.12+.
+
+To Compile:
+To dynamically load this module (assuming your copy of Apache is
+compiled to use mod_so), do:
+
+  /path/to/apxs -c mod_ecs.c
+
+Optionally, to (semi-)automatically install the module, do:
+  /path/to/apxs -i -a -n ecs mod_ecs.so
+
+Or, you can just edit your httpd.conf file yourself, adding the
+following lines:
+  LoadModule ecs_module /path/to/installed/mod_ecs.so
+  # This line needs to be after and ClearModuleList command
+  AddModule mod_ecs.c
+
+There are two ways to tell Apache that a file is an embedded ClearSilver
+CGI shared library, both are by extension.  Typically, we use the .cso
+extension.  You can either use AddHandler or AddType:
+  AddHandler ecs-cgi .cso
+  AddType application/x-ecs-cgi
+
diff -Nbru clearsilver-0.3/mod_ecs/mod_ecs.c clearsilver-0.4/mod_ecs/mod_ecs.c
--- clearsilver-0.3/mod_ecs/mod_ecs.c	Wed Dec 31 16:00:00 1969
+++ clearsilver-0.4/mod_ecs/mod_ecs.c	Fri Jan 11 17:05:31 2002
@@ -0,0 +1,832 @@
+/*
+mod_ecs - Embedded ClearSilver CGI Apache Module
+
+mod_ecs is a heavily modified version of mod_ecgi from:
+http://www.webthing.com/software/mod_ecgi.html
+
+This version is designed to run with the ClearSilver CGIKit, specifically 
+with the cgi_wrap calls from that kit.  Those calls wrap the standard CGI 
+access methods, namely environment variables and stdin/stdout, allowing 
+those calls to be replaced easily.  mod_ecs provides replacement calls which
+interface directly with the Apache internals.
+
+Additionally, mod_ecs is designed to dlopen() the shared library CGI once, 
+and keep it in memory, making the CGI almost identical in performance to a 
+regular Apache module.  The fact that your CGI will be called multiple times
+is the biggest difference you can expect from a standard ClearSilver based CGI.
+This means your code must be clean!
+
+ECS - Embedded ClearSilver
+
+Platform: UNIX only.  Anyone who wants to is welcome to port it elsewhere.
+
+=======================================================
+To COMPILE Apache with embedded CGI support, use
+	-ldl in EXTRA_LIBS
+	possibly -rdynamic in EXTRA_LFLAGS
+ I took this out of the config because its not there on freebsd4 
+ = ConfigStart
+	LIBS="$LIBS -ldl"
+ = ConfigEnd
+(or as required by your platform)
+
+OK, here's for APACI:
+ * MODULE-DEFINITION-START
+ * Name: ecs_module
+ * MODULE-DEFINITION-END
+
+=======================================================
+
+=======================================================
+BUGS
+Lots - here are some obvious ones
+	- won't work with NPH
+	- No mechanism is provided for running from an SSI
+	- Can't take part in content-negotiation
+	- No graceful cleanup if a CGI program crashes (though it's OK
+	  if the CGI fails but returns).
+	- Suspected memory leak inherited from Apache (which ignores it
+	  because it happens just before exit there).
+
+*/
+
+#include <dlfcn.h>
+#include "mod_ecs.h"
+
+#include "httpd.h"
+#include "http_config.h"
+#include "http_request.h"
+#include "http_core.h"
+#include "http_protocol.h"
+#include "http_main.h"
+#include "http_log.h"
+#include "util_script.h"
+#include "http_conf_globals.h"
+
+module ecs_module;
+
+/* Configuration stuff */
+
+#define log_reason(reason,name,r) ap_log_error(APLOG_MARK,APLOG_ERR,(r)->server,(reason),(name))
+#define log_scripterror(r,conf,ret,error) (log_reason((error),(r)->filename,(r)),ret)
+
+char** ecs_create_argv(pool*,char*,char*,char*,char*,const char*);
+
+/****************************************************************
+ *
+ * Actual CGI handling...
+ */
+const int ERROR = 500;
+const int INTERNAL_REDIRECT = 3020;
+
+#undef ECS_DEBUG
+
+/******************************************************************
+ * cgiwrap routines
+ *   We've replaced all the normal CGI api calls with calls to the 
+ *   appropriate cgiwrap routines instead.  Then, we provide versions of
+ *   the cgiwrap callback here that interface directly with apache.  We
+ *   need to mimic a bunch of the stuff that apache does in mod_cgi in
+ *   order to implement the output portion of the CGI spec.
+ */
+typedef struct header_buf {
+  char *buf;
+  int len;
+  int max;
+  int loc;
+  int nonl;
+} HEADER_BUF;
+
+typedef struct wrap_data {
+  HEADER_BUF hbuf;
+  int end_of_header;
+  int returns;
+  request_rec *r;
+} WRAPPER_DATA;
+
+static int buf_getline (char *idata, int ilen, char *odata, int olen, int *nonl)
+{
+  char *eol;
+  int len;
+
+  *nonl = 1;
+  eol = strchr (idata, '\n');
+  if (eol == NULL)
+  {
+    len = ilen;
+  }
+  else
+  {
+    *nonl = 0;
+    len = eol - idata + 1;
+  }
+  if (len > olen) len = olen;
+  memcpy (odata, idata, len);
+  odata[len] = '\0';
+  return len;
+}
+
+static int h_getline (char *buf, int len, void *h)
+{
+  HEADER_BUF *hbuf = (HEADER_BUF *)h;
+  int ret;
+
+  buf[0] = '\0';
+  if (hbuf->loc > hbuf->len)
+    return 0;
+
+  ret = buf_getline (hbuf->buf + hbuf->loc, hbuf->len - hbuf->loc, buf, len, &(hbuf->nonl));
+  hbuf->loc += ret;
+#if ECS_DEBUG>1
+  fprintf (stderr, "h_getline: [%d] %s\n", ret, buf);
+#endif
+  return ret;
+}
+
+static int header_write (HEADER_BUF *hbuf, char *data, int dlen)
+{
+  char buf[1024];
+  int done, len;
+  int nonl = hbuf->nonl;
+
+  done = 0;
+  while (done < dlen)
+  {
+    nonl = hbuf->nonl;
+    len = buf_getline (data + done, dlen - done, buf, sizeof(buf), &(hbuf->nonl));
+    if (len == 0)
+      break;
+    done += len;
+    if (hbuf->len + len > hbuf->max)
+    {
+      hbuf->max *= 2;
+      if (hbuf->len + len > hbuf->max)
+      {
+	hbuf->max += len + 1;
+      }
+      hbuf->buf = (char *) realloc ((void *)(hbuf->buf), hbuf->max);
+    }
+    memcpy (hbuf->buf + hbuf->len, buf, len);
+    hbuf->len += len;
+    if (!nonl && (buf[0] == '\n' || buf[0] == '\r'))
+    {
+      /* end of headers */
+      return done;
+    }
+  }
+
+  return 0;
+}
+
+/* The normal CGI module passes the returned data through
+ * ap_scan_script_header().  We can't do that directly, since we don't
+ * have a constant stream of data, so we buffer the header into our own
+ * structure, and call ap_scan_script_header_err_core() with our own
+ * getline() function to walk the header buffer we have.  We could
+ * probably get some speed improvement by keeping the header buffer
+ * between runs, instead of growing it every time... for later.  Also,
+ * we currently don't use the pool allocation routines here, so we have
+ * to be very careful not to leak.  We could probably at least use the
+ * ap_register_cleanup() function to make sure we clean up our mess...
+ */
+static int wrap_write (void *data, char *buf, size_t len)
+{
+  WRAPPER_DATA *wrap = (WRAPPER_DATA *)data;
+  int wl;
+  int ret;
+
+#if ECS_DEBUG>1
+  fprintf (stderr, "wrap_write (%s, %d)\n", buf, len);
+#endif
+  if (!wrap->end_of_header)
+  {
+    wl = header_write (&(wrap->hbuf), buf, len);
+    if (wl == 0)
+    {
+      return len;
+    }
+    wrap->end_of_header = 1;
+    wrap->hbuf.loc = 0;
+#if ECS_DEBUG>1
+    fprintf (stderr, "ap_scan_script_header_err_core\n%s\n", wrap->hbuf.buf);
+#endif
+    wrap->returns = ap_scan_script_header_err_core(wrap->r, NULL, h_getline, 
+	(void *)&(wrap->hbuf));
+#if ECS_DEBUG>1
+    fprintf (stderr, "ap_scan_script_header_err_core.. done\n");
+#endif
+    if (len >= wl)
+    {
+      len = len - wl;
+      buf = buf + wl;
+    }
+
+    if (wrap->returns == OK)
+    {
+      const char* location = ap_table_get (wrap->r->headers_out, "Location");
+
+      if (location && location[0] == '/' && wrap->r->status == 200) 
+      {
+	wrap->returns = INTERNAL_REDIRECT;
+      } 
+      else if (location && wrap->r->status == 200) 
+      {
+	/* XX Note that if a script wants to produce its own Redirect
+	 * body, it now has to explicitly *say* "Status: 302"
+	 */
+	wrap->returns = REDIRECT;
+      } 
+      else 
+      {
+#ifdef ECS_DEBUG
+	fprintf (stderr, "ap_send_http_header\n");
+#endif
+	ap_send_http_header(wrap->r);
+#ifdef ECS_DEBUG
+	fprintf (stderr, "ap_send_http_header.. done\n");
+#endif
+      }
+    }
+  }
+  /* if header didn't return OK, ignore the rest */
+  if ((wrap->returns != OK) || wrap->r->header_only)
+  {
+    return len;
+  }
+#if ECS_DEBUG>1
+  fprintf (stderr, "ap_rwrite(%s,%d)\n", buf, len);
+#endif
+  ret = ap_rwrite (buf, len, wrap->r);
+#if ECS_DEBUG>1
+  fprintf (stderr, "ap_rwrite.. done\n");
+#endif
+  return ret;
+}
+
+int wrap_vprintf (void *data, char *fmt, va_list ap)
+{
+  char buf[4096];
+  int len;
+
+  len = ap_vsnprintf (buf, sizeof(buf), fmt, ap);
+  return wrap_write (data, buf, len);
+}
+
+static int wrap_read (void *data, void *buf, size_t len)
+{
+  WRAPPER_DATA *wrap = (WRAPPER_DATA *)data;
+  int ret;
+  int x = 0;
+
+#if ECS_DEBUG>1
+  fprintf (stderr, "wrap_read (%s, %d)\n", buf, len);
+#endif
+  do
+  {
+    ret = ap_get_client_block(wrap->r, buf + x, len - x);
+    if (ret <= 0) break;
+    x += ret;
+  } while (x < len);
+#if ECS_DEBUG>1
+  fprintf (stderr, "done ap_get_client_block\n");
+#endif
+  if (ret < 0) return ret;
+  return x;
+}
+
+static char *wrap_getenv (void *data, char *s)
+{
+  WRAPPER_DATA *wrap = (WRAPPER_DATA *)data;
+  char *v;
+
+  v = (char *) ap_table_get (wrap->r->subprocess_env, s);
+  if (v) return strdup(v);
+  return NULL;
+}
+
+static int wrap_putenv (void *data, char *k, char *v)
+{
+  WRAPPER_DATA *wrap = (WRAPPER_DATA *)data;
+
+  ap_table_set (wrap->r->subprocess_env, k, v);
+
+  return 0;
+}	
+
+static char *wrap_iterenv (void *data, int x, char **k, char **v)
+{
+  WRAPPER_DATA *wrap = (WRAPPER_DATA *)data;
+  array_header *env = ap_table_elts(wrap->r->subprocess_env);
+  table_entry *entry = (table_entry*)env->elts;
+
+  if (x >= env->nelts) return 0;
+
+  if (entry[x].key == NULL || entry[x].val == NULL)
+    return 0;
+
+  *k = strdup(entry[x].key);
+  *v = strdup(entry[x].val);
+
+  return 0;
+}	
+
+/*************************************************************************
+ * Actual mod_ecs data structures for configuration
+ */
+
+typedef void (*InitFunc)();
+typedef void (*CleanupFunc)();
+typedef int (*CGIMainFunc)(int,char**,char**);
+typedef int (*WrapInitFunc)(void *,void *,void*,void*,void*,void*,void*);
+
+typedef struct {
+  const char *libpath;
+  ap_os_dso_handle_t dlib;
+} ecs_deplibs;
+
+typedef struct {
+  const char *libpath;
+  ap_os_dso_handle_t dlib;
+  WrapInitFunc wrap_init;
+  CGIMainFunc start;
+  time_t mtime;
+  int loaded;
+} ecs_manager;
+
+typedef struct {
+  array_header *deplibs;
+  array_header *handlers;
+  int fork_enabled;
+  int reload_enabled;
+} ecs_server_conf;
+
+const char *ECSInit = "ECSInit";
+const char *ECSCleanUp = "ECSCleanup";
+const char *WrapInit = "cgiwrap_init_emu";
+const char *CGIMain = "main";
+
+static void dummy (ap_os_dso_handle_t dlhandle)
+{
+}
+
+static void slib_cleanup (ap_os_dso_handle_t dlhandle)
+{
+  CleanupFunc cleanupFunc;
+  if ((cleanupFunc = (CleanupFunc)ap_os_dso_sym(dlhandle, ECSCleanUp))) {
+    (*cleanupFunc)();
+  }
+  ap_os_dso_unload(dlhandle);
+#ifdef ECS_DEBUG
+  fprintf(stderr, "Unloading handle %d", dlhandle);
+#endif
+}
+
+void *create_ecs_config (pool *p, server_rec *dummy)
+{
+  ecs_server_conf *new = ap_palloc (p, sizeof(ecs_server_conf));
+  new->deplibs = ap_make_array(p,1,sizeof(ecs_deplibs));
+  new->handlers = ap_make_array(p,1,sizeof(ecs_manager));
+  new->fork_enabled = 0;
+  new->reload_enabled = 0;
+  return (void *) new;
+}
+
+char** e_setup_cgi_env (request_rec* r)
+{
+  char** env;
+
+  ap_add_common_vars(r);
+  ap_add_cgi_vars(r);
+  env = ap_create_environment(r->pool,r->subprocess_env);
+
+  return env;
+}
+
+const char *set_dep_lib (cmd_parms *parms, void *dummy, char *arg)
+{
+  ecs_server_conf *cls = ap_get_module_config (parms->server->module_config,
+      &ecs_module);
+  ecs_deplibs *entry;
+  ap_os_dso_handle_t dlhandle;
+  InitFunc init_func;
+
+  if ((dlhandle = ap_os_dso_load(arg)) == NULL) {
+    return ap_os_dso_error();
+  }
+
+  if ((init_func = (InitFunc)ap_os_dso_sym(dlhandle, ECSInit))) {
+    (*init_func)();
+  }
+
+  ap_register_cleanup (cls->deplibs->pool, dlhandle, slib_cleanup, slib_cleanup);
+
+  entry = (ecs_deplibs*)ap_push_array(cls->deplibs);
+  entry->libpath = ap_pstrdup(cls->deplibs->pool, arg);
+  entry->dlib = dlhandle;
+
+  return NULL;
+}
+
+/* Load an ecs shared library */
+static const char *load_library (ap_pool *p, ecs_manager *entry, int do_stat, char *prefix)
+{
+  ap_os_dso_handle_t dlhandle;
+  InitFunc init_func;
+  CGIMainFunc cgi_main;
+  WrapInitFunc wrap_init;
+  char *err;
+  struct stat s;
+
+  if (do_stat)
+  {
+    if (stat(entry->libpath, &s) == -1)
+    {
+      err = ap_psprintf (p, "Failed to stat library file %s: %d", entry->libpath, errno);
+      return err;
+    }
+    entry->mtime = s.st_mtime;
+  }
+
+  if (entry->loaded == 1)
+  {
+    fprintf (stderr, "Warning: attempting to reload %s but it's already loaded\n", entry->libpath);
+  }
+
+  /* This does a RTLD_NOW, if we want lazy, we're going to have to do it
+   * ourselves */
+  if ((dlhandle = ap_os_dso_load(entry->libpath)) == NULL) {
+    return ap_os_dso_error();
+  }
+
+  if (entry->dlib == dlhandle)
+  {
+    fprintf (stderr, "Warning: Reload of %s returned same handle\n", entry->libpath);
+  }
+
+  if ((init_func = (InitFunc)ap_os_dso_sym(dlhandle, ECSInit))) {
+    (*init_func)();
+  }
+  if (!(wrap_init = (WrapInitFunc)ap_os_dso_sym(dlhandle, WrapInit))) {
+    err = ap_psprintf (p, "Failed to find wrap init function %s in shared object: %s", WrapInit, dlerror());
+    ap_os_dso_unload(dlhandle);
+    return err;
+  }
+  if (!(cgi_main = (CGIMainFunc)ap_os_dso_sym(dlhandle, CGIMain))) {
+    err = ap_psprintf (p, "Failed to find entry function %s in shared object: %s", CGIMain, dlerror());
+    ap_os_dso_unload(dlhandle);
+    return err;
+  }
+
+  /* Um, this may be a problem... */
+  ap_register_cleanup (p, dlhandle, slib_cleanup, dummy);
+
+  entry->dlib = dlhandle;
+  entry->wrap_init = wrap_init;
+  entry->start = cgi_main;
+  entry->loaded = 1;
+
+  fprintf (stderr, "%sLoaded library %s [%d]\n", prefix, entry->libpath, dlhandle);
+
+  return NULL;
+}
+
+const char *set_pre_lib (cmd_parms *parms, void *dummy, char *arg)
+{
+  ecs_server_conf *cls = ap_get_module_config (parms->server->module_config,
+      &ecs_module);
+  ecs_manager *entry;
+
+  entry = (ecs_manager*)ap_push_array(cls->handlers);
+  entry->libpath = ap_pstrdup(cls->handlers->pool, arg);
+
+  return load_library (cls->handlers->pool, entry, 1, "Pre");
+}
+
+const char *set_fork (cmd_parms *parms, void *dummy, int flag)
+{
+  ecs_server_conf *cls = ap_get_module_config (parms->server->module_config,
+      &ecs_module);
+
+  cls->fork_enabled = (flag ? 1 : 0);
+
+  return NULL;
+}
+
+const char *set_reload (cmd_parms *parms, void *dummy, int flag)
+{
+  ecs_server_conf *cls = ap_get_module_config (parms->server->module_config,
+      &ecs_module);
+
+  cls->reload_enabled = (flag ? 1 : 0);
+
+  return NULL;
+}
+
+static ecs_manager *findHandler(array_header *a, char *file)
+{
+  ecs_manager *list = (ecs_manager*)(a->elts);
+  int i;
+
+  for (i = 0; i < a->nelts; i++)
+  {
+    if (!strcmp(list[i].libpath, file))
+      return &(list[i]);
+  }
+  return NULL;
+}
+
+static int run_dl_cgi (ecs_server_conf *sconf, request_rec* r, char* argv0)
+{
+  int ret = 0;
+  void* handle;
+  int cgi_status;
+  int argc;
+  char** argv;
+  WRAPPER_DATA *wdata;
+  ecs_manager *handler;
+  const char *err;
+
+  char** envp = e_setup_cgi_env(r);
+
+  /* Find/open library */
+  handler = findHandler (sconf->handlers, r->filename);
+  if (handler == NULL)
+  {
+    ecs_manager my_handler;
+    my_handler.libpath = ap_pstrdup(sconf->handlers->pool, r->filename);
+    err = load_library(sconf->handlers->pool, &my_handler, 1, "");
+    if (err != NULL)
+    {
+      log_reason("Error opening library:", err, r);
+      ret = ERROR;
+    }
+    else
+    {
+      handler = (ecs_manager*)ap_push_array(sconf->handlers);
+      handler->dlib = my_handler.dlib;
+      handler->wrap_init = my_handler.wrap_init;
+      handler->start = my_handler.start;
+      handler->mtime = my_handler.mtime;
+      handler->loaded = my_handler.loaded;
+      handler->libpath = my_handler.libpath;
+    }
+  }
+  else if (sconf->reload_enabled)
+  {
+    struct stat s;
+    if (stat(handler->libpath, &s) == -1)
+    {
+      log_reason("Unable to stat file: ", handler->libpath, r);
+      ret = ERROR;
+    }
+    else if (!handler->loaded || (s.st_mtime > handler->mtime))
+    {
+      if (handler->loaded)
+      {
+	int x;
+	fprintf (stderr, "Unloading %s\n", handler->libpath);
+	slib_cleanup(handler->dlib);
+	/* Really unload this thing */
+	while ((x < 100) && (dlclose(handler->dlib) != -1)) x++;
+	if (x == 100) 
+	  fprintf (stderr, "dlclose() never returned -1");
+	handler->loaded = 0;
+      }
+      err = load_library(sconf->handlers->pool, handler, 0, "Re");
+      if (err != NULL)
+      {
+	log_reason("Error opening library:", err, r);
+	ret = ERROR;
+      }
+      handler->mtime = s.st_mtime;
+    }
+  }
+
+  if (!ret) {
+    if ((!r->args) || (!r->args[0]) || (ap_ind(r->args,'=') >= 0) ) 
+    {
+      argc = 1;
+      argv = &argv0;
+    } else {
+      argv = ecs_create_argv(r->pool, NULL,NULL,NULL,argv0,r->args);
+      for (argc = 0 ; argv[argc] ; ++argc);
+    }
+  }
+
+  /*  Yow ... at last we can go ...
+
+      Now, what to do if CGI crashes (aaargh)
+      Methinks an atexit ... cleanup perhaps; have to figgerout
+      what the atexit needs to invoke ... yuk!
+
+      Or maybe better to catch SIGSEGV and SIGBUS ?
+      - we don't want coredumps from someone else's bugs, do we?
+      still doesn't guarantee anything very good :-(
+
+      Ugh .. nothing better???
+   */
+  if (!ret)
+  {
+    wdata = (WRAPPER_DATA *) ap_pcalloc (r->pool, sizeof (WRAPPER_DATA));
+    /* We use malloc here because there is no pool alloc command for
+     * realloc... */
+    wdata->hbuf.buf = (char *) malloc (sizeof(char) * 1024);
+    wdata->hbuf.max = 1024;
+    wdata->r = r;
+
+#ifdef ECS_DEBUG
+    fprintf (stderr, "wrap_init()\n");
+#endif
+    handler->wrap_init(wdata, wrap_read, wrap_vprintf, wrap_write, wrap_getenv, wrap_putenv, wrap_iterenv);
+
+#ifdef ECS_DEBUG
+    fprintf (stderr, "cgi_main()\n");
+#endif
+    cgi_status = handler->start(argc,argv,envp);
+    if (cgi_status != 0)
+    {
+      /*log_reason("CGI returned error status", cgi_status, r) ;*/
+      ret = ERROR;
+    }
+
+    if (wdata->returns != OK)
+      ret = wdata->returns;
+
+    free (wdata->hbuf.buf);
+  }
+
+  return ret;
+}
+
+int run_xcgi (ecs_server_conf *conf, request_rec* r, char* argv0)
+{
+  int len_read;
+  char argsbuffer[HUGE_STRING_LEN];
+  int ret = 0;
+
+  ret = run_dl_cgi (conf, r, argv0);
+
+  if (ret == INTERNAL_REDIRECT)
+  {
+    const char* location = ap_table_get (r->headers_out, "Location");
+
+    /* This redirect needs to be a GET no matter what the original
+     * method was.
+     */
+    r->method = ap_pstrdup(r->pool, "GET");
+    r->method_number = M_GET;
+
+    /* We already read the message body (if any), so don't allow
+     * the redirected request to think it has one.  We can ignore 
+     * Transfer-Encoding, since we used REQUEST_CHUNKED_ERROR.
+     */
+    ap_table_unset(r->headers_in, "Content-Length");
+
+    ap_internal_redirect_handler (location, r);
+    return OK;
+  } 
+
+  return ret;
+}
+
+int ecs_handler (request_rec* r)
+{
+  int retval;
+  char *argv0;
+  int is_included = !strcmp (r->protocol, "INCLUDED");
+  void *sconf = r->server->module_config;
+  ecs_server_conf *conf =
+    (ecs_server_conf *)ap_get_module_config(sconf, &ecs_module);
+
+  ap_error_log2stderr(r->server);
+#ifdef ECS_DEBUG
+  fprintf(stderr, "running ecs_handler %s\n", r->filename);
+#endif
+
+  if((argv0 = strrchr(r->filename,'/')) != NULL)
+    argv0++;
+  else argv0 = r->filename;
+
+  if (!(ap_allow_options (r) & OPT_EXECCGI) )
+    return log_scripterror(r, conf, FORBIDDEN,
+	"Options ExecCGI is off in this directory");
+
+  if (S_ISDIR(r->finfo.st_mode))
+    return log_scripterror(r, conf, FORBIDDEN,
+	"attempt to invoke directory as script");
+  if (r->finfo.st_mode == 0)
+    return log_scripterror(r, conf, NOT_FOUND,
+	"file not found or unable to stat");
+
+#ifdef ECS_DEBUG
+  fprintf (stderr, "ap_setup_client_block\n");
+#endif
+  if ((retval = ap_setup_client_block(r, REQUEST_CHUNKED_ERROR)))
+    return retval;
+
+#ifdef ECS_DEBUG
+  fprintf (stderr, "before run\n");
+#endif
+  return run_xcgi(conf, r, argv0);
+}
+
+handler_rec ecs_handlers[] = {
+  { ECS_MAGIC_TYPE, ecs_handler },
+  { "ecs-cgi", ecs_handler},
+  { NULL }
+};
+
+command_rec ecs_cmds[] = {
+ { "ECSFork", set_fork, NULL, OR_FILEINFO, FLAG,
+   "On or off to enable or disable (default) forking before calling cgi_main" },
+ { "ECSReload", set_reload, NULL, OR_FILEINFO, FLAG,
+   "On or off to enable or disable (default) checking if the shared library\n" \
+   "  has changed and reloading it if it has"},
+ { "ECSDepLib", set_dep_lib, NULL, RSRC_CONF, TAKE1,
+   "The location of a dependent lib to dlopen during init"},
+ { "ECSPreload", set_pre_lib, NULL, RSRC_CONF, TAKE1,
+   "The location of a shared lib handler to preload during init"},
+ { NULL }
+};
+
+module ecs_module = {
+   STANDARD_MODULE_STUFF,
+   NULL,			/* initializer */
+   NULL,			/* dir config creater */
+   NULL,			/* dir merger --- default is to override */
+   create_ecs_config,		/* server config */
+   NULL, /*merge_ecs_config,*/	       	/* merge server config */
+   ecs_cmds,			/* command table */
+   ecs_handlers,		/* handlers */
+   NULL,			/* filename translation */ 
+   NULL,			/* check_user_id */
+   NULL,			/* check auth */
+   NULL,			/* check access */
+   NULL,			/* type_checker */
+   NULL,			/* fixups */
+   NULL,			/* logger */
+#if MODULE_MAGIC_NUMBER >= 19970103
+   NULL,       			/* [3] header parser */ 
+#endif
+#if MODULE_MAGIC_NUMBER >= 19970719
+   NULL,       			/* process initializer */
+#endif
+#if MODULE_MAGIC_NUMBER >= 19970728
+   NULL,       			/* process exit/cleanup */
+#endif
+#if MODULE_MAGIC_NUMBER >= 19970902
+   NULL,       			/* [1] post read_request handling */
+#endif
+};
+
+
+/* Here's some stuff that essentially duplicates util_script.c
+   This really should be merged, but if _I_ do that it'll break
+   modularity and leave users with a nasty versioning problem.
+
+   If I get a round tuit sometime, I might ask the Apache folks
+   about integrating some changes in the main source tree.
+*/
+/* If a request includes query info in the URL (stuff after "?"), and
+ * the query info does not contain "=" (indicative of a FORM submission),
+ * then this routine is called to create the argument list to be passed
+ * to the CGI script.  When suexec is enabled, the suexec path, user, and
+ * group are the first three arguments to be passed; if not, all three
+ * must be NULL.  The query info is split into separate arguments, where
+ * "+" is the separator between keyword arguments.
+ */
+char **ecs_create_argv(pool *p, char *path, char *user, char *group,
+                          char *av0, const char *args)
+{
+    int x, numwords;
+    char **av;
+    char *w;
+    int idx = 0;
+
+    /* count the number of keywords */
+
+    for (x = 0, numwords = 1; args[x]; x++)
+        if (args[x] == '+') ++numwords;
+
+    if (numwords > APACHE_ARG_MAX - 5) {
+        numwords = APACHE_ARG_MAX - 5; /* Truncate args to prevent overrun */
+    }
+    av = (char **)ap_palloc(p, (numwords + 5) * sizeof(char *));
+
+    if (path)
+        av[idx++] = path;
+    if (user)
+        av[idx++] = user;
+    if (group)
+        av[idx++] = group;
+
+    av[idx++] = av0;
+
+    for (x = 1; x <= numwords; x++) {
+        w = ap_getword_nulls(p, &args, '+');
+        ap_unescape_url(w);
+        av[idx++] = ap_escape_shell_cmd(p, w);
+    }
+    av[idx] = NULL;
+    return av;
+}
diff -Nbru clearsilver-0.3/mod_ecs/mod_ecs.h clearsilver-0.4/mod_ecs/mod_ecs.h
--- clearsilver-0.3/mod_ecs/mod_ecs.h	Wed Dec 31 16:00:00 1969
+++ clearsilver-0.4/mod_ecs/mod_ecs.h	Fri Jan 11 15:32:32 2002
@@ -0,0 +1,24 @@
+#ifndef _MOD_ECS_H_
+#define _MOD_ECS_H_ 1
+
+/* not a real .h file - just for the cgiplusplus interface */
+
+/* not an enum - want a bitfield */
+#define APACHE_CGI 0x1000
+/* need nonzero even for a default :-) */
+#define CGIPLUSPLUS 1
+#define EXTENDED_CGI 2
+#define USE_STRICT 4
+#define OPTIMISED_DESCRIPTORS 8
+#define ENV_AS_TABLE 0x10
+#define USE_CGI_STDERR 0x20
+#define STDERR_TO_HTML 0x40
+#define NPH 0x100
+#define STDIN_BUF 0x200
+#define OWN_STDOUT 0x400
+#define OWN_STDERR 0x800
+/* one day we'll get round to implementing all the above */
+
+#define ECS_MAGIC_TYPE "application/x-ecs-cgi"
+
+#endif /* _MOD_ECS_H_ */
diff -Nbru clearsilver-0.3/python/neo_util.c clearsilver-0.4/python/neo_util.c
--- clearsilver-0.3/python/neo_util.c	Tue Aug  7 14:58:46 2001
+++ clearsilver-0.4/python/neo_util.c	Tue Oct 23 00:01:35 2001
@@ -366,6 +366,32 @@
   return Py_None;
 }
 
+static PyObject * p_hdf_copy (PyObject *self, PyObject *args)
+{
+  HDFObject *ho = (HDFObject *)self;
+  HDF *src = NULL;
+  PyObject *rv, *o = NULL;
+  char *name;
+  NEOERR *err;
+
+  if (!PyArg_ParseTuple(args, "sO:copy(name, src_hdf)", &name, &o))
+    return NULL;
+
+  src = p_object_to_hdf (o);
+  if (src == NULL)
+  {
+    PyErr_Format(PyExc_TypeError, "second argument must be an HDFObject");
+    return NULL;
+  }
+
+  err = hdf_copy (ho->data, name, src);
+  if (err) return p_neo_error(err); 
+
+  rv = Py_None;
+  Py_INCREF(rv);
+  return rv;
+}
+
 static PyMethodDef HDFMethods[] =
 {
   {"getIntValue", p_hdf_get_int_value, METH_VARARGS, NULL},
@@ -383,6 +409,7 @@
   {"writeString", p_hdf_write_string, METH_VARARGS, NULL},
   {"removeTree", p_hdf_remove_tree, METH_VARARGS, NULL},
   {"dump", p_hdf_dump, METH_VARARGS, NULL},
+  {"copy", p_hdf_copy, METH_VARARGS, NULL},
   {NULL, NULL}
 };
 
diff -Nbru clearsilver-0.3/util/neo_hdf.c clearsilver-0.4/util/neo_hdf.c
--- clearsilver-0.3/util/neo_hdf.c	Fri Sep 14 17:21:30 2001
+++ clearsilver-0.4/util/neo_hdf.c	Wed Oct 10 17:26:13 2001
@@ -288,7 +288,15 @@
 
 char* hdf_obj_value (HDF *hdf)
 {
+  int count = 0;
+
   if (hdf == NULL) return NULL;
+  while (hdf->link && count < 100)
+  {
+    if (_walk_hdf (hdf->top, hdf->value, &hdf))
+      return NULL;
+    count++;
+  }
   return hdf->value;
 }
 
@@ -326,6 +334,7 @@
       hdf->alloc_value = wf;
       hdf->value = value;
     }
+    return STATUS_OK;
   }
 
   n = name;
diff -Nbru clearsilver-0.3/util/neo_misc.c clearsilver-0.4/util/neo_misc.c
--- clearsilver-0.3/util/neo_misc.c	Mon Aug  6 14:28:17 2001
+++ clearsilver-0.4/util/neo_misc.c	Wed Jan  2 16:36:42 2002
@@ -338,7 +338,7 @@
 	{
 	  if (errno == ENOENT) continue;
 	  closedir(dp);
-	  return nerr_raise_errno (NERR_SYSTEM, "Unable to stat file %s", 
+	  return nerr_raise_errno (NERR_SYSTEM, "Unable to unlink file %s", 
 	      npath);
 	}
       }
diff -Nbru clearsilver-0.3/util/neo_misc.h clearsilver-0.4/util/neo_misc.h
--- clearsilver-0.3/util/neo_misc.h	Mon Aug  6 14:28:17 2001
+++ clearsilver-0.4/util/neo_misc.h	Mon Dec 17 23:17:05 2001
@@ -24,6 +24,8 @@
 typedef char INT8;
 typedef char BOOL;
 
+#define MIN(x,y)        (((x) < (y)) ? (x) : (y))
+
 #ifndef TRUE
 #define TRUE 1
 #endif
diff -Nbru clearsilver-0.3/util/neo_str.c clearsilver-0.4/util/neo_str.c
--- clearsilver-0.3/util/neo_str.c	Mon Aug  6 14:28:17 2001
+++ clearsilver-0.4/util/neo_str.c	Mon Dec 17 23:17:05 2001
@@ -45,6 +45,15 @@
   return s;
 }
 
+void neos_lower(char *s)
+{
+  while(*s != 0) {
+    *s = tolower(*s);
+    s++;
+  }
+}
+
+
 void string_init (STRING *str)
 {
   str->buf = NULL;
diff -Nbru clearsilver-0.3/util/neo_str.h clearsilver-0.4/util/neo_str.h
--- clearsilver-0.3/util/neo_str.h	Mon Aug  6 14:28:17 2001
+++ clearsilver-0.4/util/neo_str.h	Mon Dec 17 23:17:05 2001
@@ -23,6 +23,8 @@
  */
 char *neos_strip (char *s);
 
+void neos_lower (char *s);
+
 char *sprintf_alloc (char *fmt, ...);
 char *vsprintf_alloc (char *fmt, va_list ap);
 
diff -Nbru clearsilver-0.3/util/ulocks.c clearsilver-0.4/util/ulocks.c
--- clearsilver-0.3/util/ulocks.c	Thu Sep  6 13:10:56 2001
+++ clearsilver-0.4/util/ulocks.c	Wed Jan  2 16:36:31 2002
@@ -27,7 +27,11 @@
 
   *plock = -1;
 
-  if((lock = open(file, O_WRONLY|O_NDELAY|O_APPEND|O_CREAT, 0600)) < 0) 
+  /* note the default mode of 666 is possible a security hole in that
+   * someone else can grab your lock and DoS you.  For internal use, who
+   * cares?
+   */
+  if((lock = open(file, O_WRONLY|O_NDELAY|O_APPEND|O_CREAT, 0666)) < 0) 
   {
     if (errno == ENOENT)
     {
@@ -38,7 +42,7 @@
 	err = ne_mkdirs(file, 0777);
 	*p = '/';
 	if (err != STATUS_OK) return nerr_pass(err);
-	lock = open(file, O_WRONLY|O_NDELAY|O_APPEND|O_CREAT, 0600);
+	lock = open(file, O_WRONLY|O_NDELAY|O_APPEND|O_CREAT, 0666);
       }
     }
     if (lock < 0)
@@ -67,9 +71,9 @@
 
   *plock = -1;
 
-  if((lock = open(file, O_WRONLY|O_NDELAY|O_APPEND, 0600)) < 0) {
+  if((lock = open(file, O_WRONLY|O_NDELAY|O_APPEND, 0666)) < 0) {
     if (errno == ENOENT)
-      nerr_raise (NERR_NOT_FOUND, "Unable to find lock file %s", file);
+      return nerr_raise (NERR_NOT_FOUND, "Unable to find lock file %s", file);
     return nerr_raise_errno (NERR_IO, "Unable to open lock file %s", file);
   }
 
