diff -Nbru clearsilver-0.9.3/Makefile clearsilver-0.9.6/Makefile --- clearsilver-0.9.3/Makefile Sun Aug 31 12:10:37 2003 +++ clearsilver-0.9.6/Makefile Mon Oct 20 14:19:29 2003 @@ -35,12 +35,14 @@ fi; \ done -install: all +install: all man ./mkinstalldirs $(DESTDIR)$(cs_includedir) ./mkinstalldirs $(DESTDIR)$(bindir) ./mkinstalldirs $(DESTDIR)$(libdir) + ./mkinstalldirs $(DESTDIR)$(mandir)/man3 $(INSTALL) -m 644 ClearSilver.h $(DESTDIR)$(cs_includedir)/ $(INSTALL) -m 644 cs_config.h $(DESTDIR)$(cs_includedir)/ + $(INSTALL) -m 644 man/man3/*.3 $(DESTDIR)$(mandir)/man3/ @for mdir in $(SUBDIRS); do \ if test -d $$mdir; then \ if test -f $$mdir/Makefile.PL -a ! -f $$mdir/Makefile; then \ @@ -73,14 +75,14 @@ man: @mkdir -p man/man3 @for mdir in $(SUBDIRS); do \ - scripts/document.py --owner "Neotonic, Inc." --outdir man/man3/ $$mdir/*.h; \ + scripts/document.py --owner "ClearSilver" --outdir man/man3/ $$mdir/*.h; \ done .PHONY: hdf hdf: @mkdir -p docs/hdf @for mdir in $(SUBDIRS); do \ - scripts/document.py --hdf --owner "Neotonic, Inc." --outdir docs/hdf/ $$mdir/*.h; \ + scripts/document.py --hdf --owner "ClearSilver." --outdir docs/hdf/ $$mdir/*.h; \ done changelog: @@ -105,8 +107,8 @@ mkdir -p $$mdir; \ done -CS_DISTDIR = clearsilver-0.9.3 -CS_LABEL = CLEARSILVER-0_9_3 +CS_DISTDIR = clearsilver-0.9.6 +CS_LABEL = CLEARSILVER-0_9_6 CS_FILES = README README.python INSTALL LICENSE CS_LICENSE rules.mk.in Makefile util cs cgi python scripts mod_ecs imd java-jni perl ruby dso csharp acconfig.h autogen.sh config.guess config.sub configure.in cs_config.h.in mkinstalldirs install-sh ClearSilver.h ports contrib cs_dist: rm -rf $(CS_DISTDIR) diff -Nbru clearsilver-0.9.3/cgi/cgi.c clearsilver-0.9.6/cgi/cgi.c --- clearsilver-0.9.3/cgi/cgi.c Mon Aug 18 13:41:08 2003 +++ clearsilver-0.9.6/cgi/cgi.c Wed Oct 8 16:45:40 2003 @@ -209,6 +209,7 @@ static int ExceptionsInit = 0; NERR_TYPE CGIFinished = -1; NERR_TYPE CGIUploadCancelled = -1; +NERR_TYPE CGIParseNotHandled = -1; static NEOERR *_add_cgi_env_var (CGI *cgi, char *env, char *name) { @@ -404,19 +405,19 @@ if (query) { - k = strtok_r(query, "=", &l); + k = strtok_r(query, "&", &l); while (k) { - if (l != NULL && *l == '&') + v = strchr(k, '='); + if (v == NULL) { - l++; v = ""; } else { - v = strtok_r(NULL, "&", &l); + *v = '\0'; + v++; } - if (v == NULL) v = ""; snprintf(buf, sizeof(buf), "Query.%s", cgi_url_unescape(k)); if (!(cgi->ignore_empty_form_vars && (*v == '\0'))) { @@ -451,7 +452,7 @@ err = hdf_set_value (cgi->hdf, buf, v); if (err != STATUS_OK) break; } - k = strtok_r(NULL, "=", &l); + k = strtok_r(NULL, "&", &l); } } return nerr_pass(err); @@ -645,14 +646,61 @@ return STATUS_OK; } +NEOERR *cgi_register_parse_cb(CGI *cgi, char *method, char *ctype, void *rock, + CGI_PARSE_CB parse_cb) +{ + struct _cgi_parse_cb *my_pcb; + + if (method == NULL || ctype == NULL) + return nerr_raise(NERR_ASSERT, "method and type must not be NULL to register cb"); + + my_pcb = (struct _cgi_parse_cb *) calloc(1, sizeof(struct _cgi_parse_cb)); + if (my_pcb == NULL) + return nerr_raise(NERR_NOMEM, "Unable to allocate memory to register parse cb"); + + my_pcb->method = strdup(method); + my_pcb->ctype = strdup(ctype); + if (my_pcb->method == NULL || my_pcb->ctype == NULL) + { + free(my_pcb); + return nerr_raise(NERR_NOMEM, "Unable to allocate memory to register parse cb"); + } + if (!strcmp(my_pcb->method, "*")) + my_pcb->any_method = 1; + if (!strcmp(my_pcb->ctype, "*")) + my_pcb->any_ctype = 1; + my_pcb->rock = rock; + my_pcb->parse_cb = parse_cb; + my_pcb->next = cgi->parse_callbacks; + cgi->parse_callbacks = my_pcb; + return STATUS_OK; +} + NEOERR *cgi_parse (CGI *cgi) { NEOERR *err; char *method, *type; + struct _cgi_parse_cb *pcb; method = hdf_get_value (cgi->hdf, "CGI.RequestMethod", "GET"); type = hdf_get_value (cgi->hdf, "CGI.ContentType", NULL); + /* Walk the registered parse callbacks for a matching one */ + pcb = cgi->parse_callbacks; + while (pcb != NULL) + { + if ( (pcb->any_method || !strcasecmp(pcb->method, method)) && + (pcb->any_ctype || (type && !strcasecmp(pcb->ctype, type))) ) + { + err = pcb->parse_cb(cgi, method, type, pcb->rock); + if (err && !nerr_handle(&err, CGIParseNotHandled)) + return nerr_pass(err); + } + pcb = pcb->next; + } + + /* Fallback to internal methods */ + if (!strcmp(method, "POST")) { if (type && !strcmp(type, "application/x-www-form-urlencoded")) @@ -759,6 +807,8 @@ if (err) return nerr_pass(err); err = nerr_register(&CGIUploadCancelled, "CGIUploadCancelled"); if (err) return nerr_pass(err); + err = nerr_register(&CGIUploadCancelled, "CGIParseNotHandled"); + if (err) return nerr_pass(err); ExceptionsInit = 1; } @@ -943,7 +993,7 @@ } #endif -static void html_ws_strip(STRING *str) +void cgi_html_ws_strip(STRING *str) { int ws = 0; int seen_nonws = 0; @@ -1072,7 +1122,6 @@ do_ws_strip = hdf_get_int_value (cgi->hdf, "Config.WhiteSpaceStrip", 1); dis = ne_timef(); - if (err != STATUS_OK) return nerr_pass (err); s = hdf_get_value (cgi->hdf, "cgiout.ContentType", "text/html"); if (!strcasecmp(s, "text/html")) is_html = 1; @@ -1085,8 +1134,9 @@ if (err != STATUS_OK) return nerr_pass (err); if (s) { + char *next; - e = strtok (s, ","); + e = strtok_r (s, ",", &next); while (e && !use_deflate) { if (strstr(e, "deflate") != NULL) @@ -1096,7 +1146,7 @@ } else if (strstr(e, "gzip") != NULL) use_gzip = 1; - e = strtok (NULL, ","); + e = strtok_r (NULL, ",", &next); } free (s); } @@ -1158,7 +1208,7 @@ if (do_ws_strip) { - html_ws_strip(str); + cgi_html_ws_strip(str); } if (do_debug) diff -Nbru clearsilver-0.9.3/cgi/cgi.h clearsilver-0.9.6/cgi/cgi.h --- clearsilver-0.9.3/cgi/cgi.h Thu Jul 17 12:44:08 2003 +++ clearsilver-0.9.6/cgi/cgi.h Wed Oct 8 16:45:40 2003 @@ -20,6 +20,7 @@ extern NERR_TYPE CGIFinished; extern NERR_TYPE CGIUploadCancelled; +extern NERR_TYPE CGIParseNotHandled; /* HACK: Set this value if you want to treat empty CGI Query variables as * non-existant. @@ -29,6 +30,18 @@ typedef struct _cgi CGI; typedef int (*UPLOAD_CB)(CGI *, int nread, int expected); +typedef NEOERR* (*CGI_PARSE_CB)(CGI *, char *method, char *ctype, void *rock); + +struct _cgi_parse_cb +{ + char *method; + int any_method; + char *ctype; + int any_ctype; + void *rock; + CGI_PARSE_CB parse_cb; + struct _cgi_parse_cb *next; +}; struct _cgi { @@ -42,6 +55,7 @@ int data_expected; int data_read; + struct _cgi_parse_cb *parse_callbacks; /* For line oriented reading of form-data input. Used during cgi_init * only */ @@ -75,10 +89,8 @@ * Description: cgi_init initializes the ClearSilver CGI environment, * including creating the HDF data set. It will then import * the standard CGI environment variables into that dataset, - * will parse the QUERY_STRING into the data set, will - * parse form submission from application/x-url-encoded and - * multipart/form-data POSTs into the data set, and parse - * the HTTP_COOKIE into the dat set. Note that if the + * will parse the QUERY_STRING into the data set, and parse + * the HTTP_COOKIE into the data set. Note that if the * var xdisplay is in the form data, cgi_init will attempt * to validate the value and launch the configured debugger * on the CGI program. These variables have to be @@ -94,14 +106,91 @@ * Return: NERR_PARSE - parse error in CGI input * NERR_NOMEM - unable to allocate memory * NERR_NOT_FOUND - hdf_file doesn't exist - * NERR_IO - error reading HDF file or reading CGI stdin, or - * writing data on multipart/form-data file submission */ NEOERR *cgi_init (CGI **cgi, HDF *hdf); +/* + * Function: cgi_parse - Parse incoming CGI data + * Description: We split cgi_init into two sections, one that parses + * just the basics, and the second is cgi_parse. cgi_parse + * is responsible for parsing the entity body of the HTTP + * request. This payload is typically only sent (expected) + * on POST/PUT requests, but generally this is called on + * all incoming requests. This function walks the list of + * registered parse callbacks (see cgi_register_parse_cb), + * and if none of those matches or handles the request, it + * falls back to the builtin handlers: + * POST w/ application/x-www-form-urlencoded + * POST w/ application/form-data + * PUT w/ any content type + * In general, if there is no Content-Length, then + * cgi_parse ignores the payload and doesn't raise an + * error. + * Input: cgi - a pointer to a CGI pointer + * Output: Either data populated into files and cgi->hdf, or whatever + * other side effects of your own registered callbacks. + * Return: NERR_PARSE - parse error in CGI input + * NERR_NOMEM - unable to allocate memory + * NERR_NOT_FOUND - hdf_file doesn't exist + * NERR_IO - error reading HDF file or reading CGI stdin, or + * writing data on multipart/form-data file submission + * Anything else you raise. + */ NEOERR *cgi_parse (CGI *cgi); /* + * Function: cgi_register_parse_cb - Register a parse callback + * Description: The ClearSilver CGI Kit has built-in functionality to handle + * the following methods: + * GET -> doesn't have any data except query string, which + * is processed for all methods + * POST w/ application/x-www-form-urlencoded + * POST w/ multipart/form-data + * processed as RFC2388 data into files and HDF (see + * cgi_filehandle()) + * PUT (any type) + * The entire data chunk is stored as a file, with meta + * data in HDF (similar to single files in RFC2388). + * The data is accessible via cgi_filehandle with NULL + * for name. + * To handle other methods/content types, you have to + * register your own parse function. This isn't necessary + * if you aren't expecting any data, and technically HTTP + * only allows data on PUT/POST requests (and presumably + * user defined methods). In particular, if you want to + * implement XML-RPC or SOAP, you'll have to register a + * callback here to grab the XML data chunk. Usually + * you'll want to register POST w/ application/xml or POST + * w/ text/xml (you either need to register both or + * register POST w/ * and check the ctype yourself, + * remember to nerr_raise(CGIParseNotHandled) if you aren't + * handling the POST). + * In general, your callback should: + * Find out how much data is available: + * l = hdf_get_value (cgi->hdf, "CGI.ContentLength", NULL); + * len = atoi(l); + * And read/handle all of the data using cgiwrap_read. + * See the builtin handlers for how this is done. Note + * that cgiwrap_read is not guarunteed to return all of + * the data you request (just like fread(3)) since it + * might be reading of a socket. Sorry. + * You should be careful when reading the data to watch + * for short reads (ie, end of file) and cases where the + * client sends you data ad infinitum. + * Input: cgi - a CGI struct + * method - the HTTP method you want to handle, or * for all + * ctype - the HTTP Content-Type you want to handle, or * for all + * rock - opaque data that we'll pass to your call back + * Output: None + * Return: CGIParseNotHandled if your callback doesn't want to handle + * this. This causes cgi_parse to continue walking the list of + * callbacks. + * + */ +NEOERR *cgi_register_parse_cb(CGI *cgi, char *method, char *ctype, void *rock, + CGI_PARSE_CB parse_cb); + +/* * Function: cgi_destroy - deallocate the data associated with a CGI * Description: cgi_destroy will destroy all the data associated with a * CGI, which mostly means the associated HDF and removal @@ -356,6 +445,13 @@ * Return: NERR_IO */ NEOERR *cgi_cookie_clear (CGI *cgi, char *name, char *domain, char *path); + +/* not documented *yet* */ +NEOERR *cgi_text_html_strfunc(unsigned char *str, unsigned char **ret); +NEOERR *cgi_html_strip_strfunc(unsigned char *str, unsigned char **ret); +NEOERR *cgi_html_escape_strfunc(unsigned char *str, unsigned char **ret); +NEOERR *cgi_js_escape (unsigned char *buf, unsigned char **esc); +void cgi_html_ws_strip(STRING *str); /* internal use only */ NEOERR * parse_rfc2388 (CGI *cgi); diff -Nbru clearsilver-0.9.3/cgi/html.c clearsilver-0.9.6/cgi/html.c --- clearsilver-0.9.3/cgi/html.c Wed Apr 2 15:07:28 2003 +++ clearsilver-0.9.6/cgi/html.c Thu Sep 11 19:02:40 2003 @@ -19,6 +19,7 @@ #include "util/neo_err.h" #include "util/neo_str.h" #include "html.h" +#include "cgi.h" static int has_space_formatting(unsigned char *src, int slen) { @@ -98,7 +99,7 @@ static char *EmailRe = "[^][@:;<>\\\"()[:space:][:cntrl:]]+@[-+a-zA-Z0-9]+\\.[-+a-zA-Z0-9\\.]+[-+a-zA-Z0-9]"; static char *URLRe = "((http|https|ftp|mailto):(//)?[^[:space:]>\"\t]*|www\\.[-a-z0-9\\.]+)[^[:space:];\t\">]*"; -static NEOERR *split_and_convert (unsigned char *src, int slen, STRING *out, int newlines, int space_convert) +static NEOERR *split_and_convert (unsigned char *src, int slen, STRING *out, HTML_CONVERT_OPTS *opts) { NEOERR *err = STATUS_OK; static int compiled = 0; @@ -282,7 +283,7 @@ x = ptr - src; if (src[x] == ' ') { - if (space_convert) + if (opts->space_convert) { spaces++; } @@ -311,10 +312,10 @@ else if (src[x] == '>') err = string_append (out, ">"); else if (src[x] == '\n') - if (newlines) - err = string_append (out, "
"); + if (opts->newlines_convert) + err = string_append (out, "
\n"); else if (x && src[x-1] == '\n') - err = string_append (out, "

"); + err = string_append (out, "

\n"); else err = string_append_char (out, '\n'); else if (src[x] != '\r') @@ -360,8 +361,70 @@ unsigned char last_char = src[parts[i].end-1]; int suffix=0; if (last_char == '.' || last_char == ',') { suffix=1; } - err = string_append (out, " url_class) + { + err = string_appendf (out, "class=%s ", opts->url_class); + if (err) break; + } + if (opts->url_target) + { + err = string_appendf (out, "target=\"%s\" ", opts->url_target); + if (err) break; + } + err = string_append(out, "href=\""); + if (err) break; + if (opts->bounce_url) + { + unsigned char *url, *esc_url, *new_url; + int url_len; + if (!strncasecmp(src + x, "www.", 4)) + { + url_len = 7 + parts[i].end - x - suffix; + url = (char *) malloc(url_len+1); + if (url == NULL) + { + err = nerr_raise(NERR_NOMEM, + "Unable to allocate memory to convert url"); + break; + } + strcpy(url, "http://"); + strncat(url, src + x, parts[i].end - x - suffix); + } + else + { + url_len = parts[i].end - x - suffix; + url = (char *) malloc(url_len+1); + if (url == NULL) + { + err = nerr_raise(NERR_NOMEM, + "Unable to allocate memory to convert url"); + break; + } + strncpy(url, src + x, parts[i].end - x - suffix); + url[url_len] = '\0'; + } + err = cgi_url_escape(url, &esc_url); + free(url); + if (err) { + free(esc_url); + break; + } + + new_url = sprintf_alloc(opts->bounce_url, esc_url); + free(esc_url); + if (new_url == NULL) + { + err = nerr_raise(NERR_NOMEM, "Unable to allocate memory to convert url"); + break; + } + err = string_append (out, new_url); + free(new_url); + if (err) break; + } + else + { if (!strncasecmp(src + x, "www.", 4)) { err = string_append (out, "http://"); @@ -369,6 +432,7 @@ } err = string_appendn (out, src + x, parts[i].end - x - suffix); if (err != STATUS_OK) break; + } err = string_append (out, "\">"); if (err != STATUS_OK) break; err = html_escape_alloc(src + x, parts[i].end - x - suffix, &esc); @@ -384,8 +448,15 @@ } else /* type == SC_TYPE_EMAIL */ { - err = string_append (out, "mailto_class) + { + err = string_appendf (out, "class=%s ", opts->mailto_class); + if (err) break; + } + err = string_append(out, "href=\"mailto:"); + if (err) break; err = string_appendn (out, src + x, parts[i].end - x); if (err != STATUS_OK) break; err = string_append (out, "\">"); @@ -448,21 +519,46 @@ NEOERR *convert_text_html_alloc (unsigned char *src, int slen, unsigned char **out) { + return nerr_pass(convert_text_html_alloc_options(src, slen, out, NULL)); +} + +NEOERR *convert_text_html_alloc_options (unsigned char *src, int slen, unsigned char **out, HTML_CONVERT_OPTS *opts) +{ NEOERR *err; STRING out_s; - int formatting; + int formatting = 0; + HTML_CONVERT_OPTS my_opts; string_init(&out_s); + if (opts == NULL) + { + opts = &my_opts; + opts->bounce_url = NULL; + opts->url_class = NULL; + opts->url_target = "_blank"; + opts->mailto_class = NULL; + opts->long_lines = 0; + opts->space_convert = 0; + opts->newlines_convert = 1; + opts->longline_width = 75; /* This hasn't been used in a while, actually */ + opts->check_ascii_art = 1; + } + do { + if (opts->check_ascii_art) + { formatting = has_space_formatting (src, slen); + if (formatting) opts->space_convert = 1; + } if (formatting == 2) { /* Do

 formatting */
+      opts->newlines_convert = 1;
       err = string_append (&out_s, "");
       if (err != STATUS_OK) break;
-      err = split_and_convert(src, slen, &out_s, 1, 1);
+      err = split_and_convert(src, slen, &out_s, opts);
       if (err != STATUS_OK) break;
       err = string_append (&out_s, "");
       if (err != STATUS_OK) break;
@@ -472,7 +568,7 @@
     else
     {
       /* int nl = has_long_lines (src, slen); */
-      err = split_and_convert(src, slen, &out_s, 1, formatting);
+      err = split_and_convert(src, slen, &out_s, opts);
     }
   } while (0);
   if (err != STATUS_OK) 
diff -Nbru clearsilver-0.9.3/cgi/html.h clearsilver-0.9.6/cgi/html.h
--- clearsilver-0.9.3/cgi/html.h	Fri Mar 14 14:58:02 2003
+++ clearsilver-0.9.6/cgi/html.h	Thu Sep 11 18:33:02 2003
@@ -17,7 +17,20 @@
 
 __BEGIN_DECLS
 
+typedef struct _text_html_opts {
+    char *bounce_url;
+    char *url_class;
+    char *url_target;
+    char *mailto_class;
+    int long_lines;
+    int space_convert;
+    int newlines_convert;
+    int longline_width;
+    int check_ascii_art;
+} HTML_CONVERT_OPTS;
+
 NEOERR *convert_text_html_alloc (unsigned char *src, int slen, unsigned char **out);
+NEOERR *convert_text_html_alloc_options (unsigned char *src, int slen, unsigned char **out, HTML_CONVERT_OPTS *opts);
 NEOERR *html_escape_alloc (unsigned char *src, int slen, unsigned char **out);
 NEOERR *html_strip_alloc(unsigned char *src, int slen, unsigned char **out);
 
diff -Nbru clearsilver-0.9.3/configure.in clearsilver-0.9.6/configure.in
--- clearsilver-0.9.3/configure.in	Sun Aug 31 10:33:24 2003
+++ clearsilver-0.9.6/configure.in	Mon Sep 22 19:09:38 2003
@@ -105,13 +105,13 @@
       cs_cv_need_reentrant=yes
       AC_MSG_RESULT(yes)],[
       cs_cv_missing=yes
-      AC_MSG_RESULT(no)])])])
+      AC_MSG_RESULT(no)])])], [cs_cv_missing=yes])
 
 AC_CHECK_FUNCS(gmtime_r, [
   AC_MSG_CHECKING(whether gmtime_r is declared)
   AC_EGREP_CPP(gmtime_r,[
 #include ],[
-    AC_DEFINE(HAVE_LOCALTIME_R)
+    AC_DEFINE(HAVE_GMTIME_R)
     AC_MSG_RESULT(yes)],[
     AC_MSG_RESULT(no)
     AC_MSG_CHECKING(whether gmtime_r with -D_REENTRANT is declared)
@@ -121,13 +121,13 @@
       cs_cv_need_reentrant=yes
       AC_MSG_RESULT(yes)],[
       cs_cv_missing=yes
-      AC_MSG_RESULT(no)])])])
+      AC_MSG_RESULT(no)])])], [cs_cv_missing=yes])
 
 AC_CHECK_FUNCS(strtok_r, [
   AC_MSG_CHECKING(whether strtok_r is declared)
   AC_EGREP_CPP(strtok_r,[
 #include ],[
-    AC_DEFINE(HAVE_LOCALTIME_R)
+    AC_DEFINE(HAVE_STRTOK_R)
     AC_MSG_RESULT(yes)],[
     AC_MSG_RESULT(no)
     AC_MSG_CHECKING(whether strtok_r with -D_REENTRANT is declared)
@@ -137,7 +137,7 @@
       cs_cv_need_reentrant=yes
       AC_MSG_RESULT(yes)],[
       cs_cv_missing=yes
-      AC_MSG_RESULT(no)])])])
+      AC_MSG_RESULT(no)])])], [cs_cv_missing=yes])
 
 AC_CHECK_FUNC(mkstemp, [AC_DEFINE(HAVE_MKSTEMP)], [cs_cv_missing=yes])
 if test $cs_cv_missing = yes; then
diff -Nbru clearsilver-0.9.3/cs_config.h.in clearsilver-0.9.6/cs_config.h.in
--- clearsilver-0.9.3/cs_config.h.in	Fri Aug  8 23:55:25 2003
+++ clearsilver-0.9.6/cs_config.h.in	Mon Sep 22 20:49:34 2003
@@ -78,8 +78,14 @@
 /* Does your system have the vsnprintf() call? */
 #undef HAVE_VSNPRINTF
 
+/* Does your system have the strtok_r() call? */
+#undef HAVE_STRTOK_R
+
 /* Does your system have the localtime_r() call? */
 #undef HAVE_LOCALTIME_R
+
+/* Does your system have the gmtime_r() call? */
+#undef HAVE_GMTIME_R
 
 /* Does your system have the mkstemp() call? */
 #undef HAVE_MKSTEMP
diff -Nbru clearsilver-0.9.3/java-jni/Makefile clearsilver-0.9.6/java-jni/Makefile
--- clearsilver-0.9.3/java-jni/Makefile	Mon Aug 18 13:09:20 2003
+++ clearsilver-0.9.6/java-jni/Makefile	Wed Sep  3 15:38:47 2003
@@ -51,7 +51,7 @@
 	LD_LIBRARY_PATH=$(NEOTONIC_ROOT)/java-jni; export LD_LIBRARY_PATH; \
 	CLASSPATH=$(NEO_UTIL_JAVA_JAR):.; export CLASSPATH; \
 	$(JAVA_PATH)/bin/java CSTest > javatest.out; \
-	diff -brief javatest.out javatest.gold 2>%1 > /dev/null; \
+	diff -brief javatest.out javatest.gold  > /dev/null 2>&1; \
 	return_code=$$?; \
 	if [ $$return_code -ne 0 ]; then \
 	  diff javatest.out javatest.gold > javatest.err; \
diff -Nbru clearsilver-0.9.3/ports/rpm/clearsilver.spec clearsilver-0.9.6/ports/rpm/clearsilver.spec
--- clearsilver-0.9.3/ports/rpm/clearsilver.spec	Sun Aug 31 12:07:39 2003
+++ clearsilver-0.9.6/ports/rpm/clearsilver.spec	Mon Oct 20 14:19:30 2003
@@ -53,11 +53,11 @@
 
 Summary: Neotonic ClearSilver
 Name: clearsilver
-Version: 0.9.3
+Version: 0.9.6
 Release: 1
 Copyright: Open Source - Neotonic ClearSilver License (Apache 1.1 based)
 Group: Development/Libraries
-Source: http://www.clearsilver.net/downloads/clearsilver-0.9.3.tar.gz
+Source: http://www.clearsilver.net/downloads/clearsilver-0.9.5.tar.gz
 URL: http://www.clearsilver.net/
 Vendor: Neotonic Software Corporation, Inc.
 Packager: Brandon Long 
@@ -188,6 +188,7 @@
 %{__prefix}/lib/libneo_utl.a
 %{__prefix}/bin/static.cgi
 %{__prefix}/bin/cstest
+%{__prefix}/man/man3
 
 %if %{with_python_subpackage}
 %files python
diff -Nbru clearsilver-0.9.3/python/Makefile clearsilver-0.9.6/python/Makefile
--- clearsilver-0.9.3/python/Makefile	Thu Jul 24 11:36:50 2003
+++ clearsilver-0.9.6/python/Makefile	Thu Sep 11 19:02:24 2003
@@ -23,7 +23,7 @@
 
 all: $(TARGETS)
 
-$(NEO_UTIL_SO): setup.py
+$(NEO_UTIL_SO): setup.py $(NEO_UTIL_SRC) $(DEP_LIBS)
 	$(PYTHON) setup.py build_ext --inplace
 
 OLD_NEO_UTIL_SO:
diff -Nbru clearsilver-0.9.3/python/examples/base/hdfhelp.py clearsilver-0.9.6/python/examples/base/hdfhelp.py
--- clearsilver-0.9.3/python/examples/base/hdfhelp.py	Wed Aug 20 22:57:16 2003
+++ clearsilver-0.9.6/python/examples/base/hdfhelp.py	Sat Sep 13 03:25:00 2003
@@ -33,7 +33,7 @@
 #      return hdfhelp.HdfItemList
 #
 
-import string
+import string, os
 import neo_cgi
 import neo_cs
 import neo_util
@@ -85,7 +85,10 @@
         return time.strftime("%m/%d/%Y %I:%M%p",then_tuple)
 
 class HdfRow(odb.Row):
-    def hdfExport(self,prefix,hdf_dataset,skip_fields = None, translate_dict = None):
+    def hdfExport(self, prefix, hdf_dataset, *extra, **extranamed):
+        skip_fields = extranamed.get("skip_fields", None)
+        translate_dict = extranamed.get("translate_dict", None)
+        tz = extranamed.get("tz", "US/Pacific")
 
         for col_name,value in self.items():
             if skip_fields and (col_name in skip_fields):
@@ -96,27 +99,53 @@
                 col_type = odb.kVarString
                 col_options = {}
             
-	    if (col_name != "value") and (value is not None):
+	    if (value is not None):
+                if col_options.get("no_export",0): continue
 		if type(value) in [ type(0), type(0L) ]:
 		    hdf_dataset.setValue(prefix + "." + col_name,"%d" % value)
+                elif type(value) == type(1.0):
+                    if int(value) == value:
+                        hdf_dataset.setValue(prefix + "." + col_name,"%d" % value)
+                    else:
+                        hdf_dataset.setValue(prefix + "." + col_name,"%0.2f" % value)
 		else:
+                    if col_type == odb.kReal:
+                        log("why are we here with this value: %s" % value)
                     if translate_dict:
                         for k,v in translate_dict.items():
                             value = string.replace(value,k,v)
-		    hdf_dataset.setValue(prefix + "." + col_name,neo_cgi.htmlEscape(value))
+		    hdf_dataset.setValue(prefix + "." + col_name,neo_cgi.htmlEscape(str(value)))
                 if col_options.get("int_date",0):
                     hdf_dataset.setValue(prefix + "." + col_name + ".string",renderDate(value))
                     hdf_dataset.setValue(prefix + "." + col_name + ".day_string",renderDate(value,day=1))
+                    if value: neo_cgi.exportDate(hdf_dataset, "%s.%s" % (prefix, col_name), tz, value)
 
-
+		if col_options.has_key("enum_values"):
+		    enum = col_options["enum_values"]
+		    hdf_dataset.setValue(prefix + "." + col_name + ".enum",
+					 str(enum.get(value,'')))
 
 class HdfItemList(UserList.UserList):
     def hdfExport(self,prefix,hdf_dataset,*extra,**extranamed):
+        export_by = extranamed.get("export_by", None)
 	n = 0
 	for row in self:
+            if export_by is not None:
+                n = row[export_by]
 	    row.hdfExport("%s.%d" % (prefix,n),hdf_dataset,*extra,**extranamed)
 	    n = n + 1
 
+def setList(hdf, prefix, lst):
+    hdf.setValue(prefix+".0", str(len(lst)))
+    for n in range(len(lst)):
+        hdf.setValue(prefix+".%d" %(n+1), lst[n]);
+
+def getList(hdf, name):
+    lst = []
+    for n in range(hdf.getIntValue(name,0)):
+        lst.append(hdf.getValue(name+".%d" %(n+1), ""))
+
+    return lst
 
 def eval_cs(hdf,a_cs_string):
     cs = neo_cs.CS(hdf)
diff -Nbru clearsilver-0.9.3/python/examples/base/odb.py clearsilver-0.9.6/python/examples/base/odb.py
--- clearsilver-0.9.3/python/examples/base/odb.py	Wed Aug 20 22:57:16 2003
+++ clearsilver-0.9.6/python/examples/base/odb.py	Thu Sep 11 03:09:47 2003
@@ -51,7 +51,6 @@
 #    list_rows = tbl.fetchRows( ('login', "foo") )
 #
 
-import tstart
 
 import string
 import sys, zlib
@@ -361,7 +360,7 @@
 
     def d_addColumn(self,col_name,ctype,size=None,primarykey = 0, notnull = 0,indexed=0,
 		    default=None,unique=0,autoincrement=0,safeupdate=0,enum_values = None,
-                    relations=None,compress_ok=0,int_date=0):
+                    relations=None,compress_ok=0,int_date=0,no_export=0):
 
 	self.__checkColumnLock()
 
@@ -381,6 +380,8 @@
 	    options['notnull']       = notnull
 	if size:
 	    options['size']          = size
+        if no_export:
+            options['no_export']     = no_export
         if int_date:
             if ctype != kInteger:
                 raise eInvalidData, "can't flag columns int_date unless they are kInteger"
diff -Nbru clearsilver-0.9.3/python/examples/base/wordwrap.py clearsilver-0.9.6/python/examples/base/wordwrap.py
--- clearsilver-0.9.3/python/examples/base/wordwrap.py	Wed Dec 31 16:00:00 1969
+++ clearsilver-0.9.6/python/examples/base/wordwrap.py	Fri Sep 12 16:05:57 2003
@@ -0,0 +1,50 @@
+"""
+WordWrapping
+"""
+
+
+import os, sys, string, time, getopt
+import re
+
+def WordWrap(text, cols=70, detect_paragraphs = 0, is_header = 0):
+  text =  string.replace(text,"\r\n", "\n") # remove CRLF
+  def nlrepl(matchobj):
+    if matchobj.group(1) != ' ' and matchobj.group(2) != ' ':
+      repl_with = ' '
+    else:
+      repl_with = ''
+
+    return matchobj.group(1) + repl_with + matchobj.group(2)
+
+  if detect_paragraphs:
+    text = re.sub("([^\n])\n([^\n])",nlrepl,text)
+
+  body = []
+  i = 0
+  j = 0
+  ltext = len(text)
+
+  while i hdftest.out; \
-	diff -brief hdftest.out hdftest.gold 2>%1 > /dev/null; \
+	diff --brief hdftest.out hdftest.gold > /dev/null 2>&1; \
 	return_code=$$?; \
 	if [ $$return_code -ne 0 ]; then \
 	  diff hdftest.out hdftest.gold > hdftest.err; \
diff -Nbru clearsilver-0.9.3/util/filter.h clearsilver-0.9.6/util/filter.h
--- clearsilver-0.9.3/util/filter.h	Fri May 10 17:41:07 2002
+++ clearsilver-0.9.6/util/filter.h	Tue Sep 30 02:07:12 2003
@@ -19,8 +19,70 @@
 #include "util/neo_misc.h"
 #include "util/neo_err.h"
 
+/*
+ * Function: filter_wait - wrap waitpid to decode the exitcode and why
+ *           your filter quit
+ * Description: filter_wait wraps the waitpid call and raises an error
+ *              (with description) if the call failed.  Note that if the
+ *              ask for the exitcode and the process exited with a code
+ *              other than zero, we don't raise an error.  If you don't
+ *              ask for the exitcode, and it is non-zero, we raise an
+ *              error
+ * Input: pid -> the process identifier to wait for
+ *        options -> the options to pass to waitpid (see wait(2))
+ * Output: exitcode -> the exitcode if the process existed normally
+ * Returns: NERR_SYSTEM, NERR_ASSERT
+ */
 NEOERR *filter_wait(pid_t pid, int options, int *exitcode);
+
+/*
+ * Function: filter_create_fd - Create a sub process and return the
+ * requested pipes
+ * Description: filter_create_fd and filter_create_fp are what popen
+ *              should have been: a mechanism to create sub processes
+ *              and have pipes to all their input/output.  The concept
+ *              was taken from mutt, though python has something similar
+ *              with popen3/popen4.  You control which pipes the
+ *              function returns by the fdin/fdout/fderr arguments.  A
+ *              NULL value means "don't create a pipe", a pointer to an
+ *              int will cause the pipes to be created and the value
+ *              of the file descriptor stored in the int.  You will have
+ *              to close(2) the file descriptors yourself.
+ * Input: cmd -> the sub command to execute.  Will be executed with
+ *               /bin/sh -c
+ *        fdin -> pointer to return the stdin pipe, or NULL if you don't
+ *                want the stdin pipe
+ *        fdout -> pointer to return the stdout pipe, or NULL if you don't
+ *                 want the stdout pipe
+ *        fderr -> pointer to return the stderr pipe, or NULL if you don't
+ *                 want the stderr pipe
+ * Output: fdin -> the stdin file descriptor of the sub process
+ *         fdout -> the stdout file descriptor of the sub process
+ *         fderr -> the stderr file descriptor of the sub process
+ *         pid -> the pid of the sub process
+ * Returns: NERR_SYSTEM
+ */
 NEOERR *filter_create_fd(char *cmd, int *fdin, int *fdout, int *fderr, pid_t *pid);
+
+/*
+ * Function: filter_create_fp - similar to filter_create_fd except with
+ *           buffered FILE* 
+ * Description: filter_create_fp is identical to filter_create_fd,
+ *              except each of the pipes is wrapped in a buffered stdio FILE 
+ * Input: cmd -> the sub command to execute.  Will be executed with
+ *               /bin/sh -c
+ *        in -> pointer to return the stdin pipe, or NULL if you don't
+ *              want the stdin pipe
+ *        out -> pointer to return the stdout pipe, or NULL if you don't
+ *               want the stdout pipe
+ *        err -> pointer to return the stderr pipe, or NULL if you don't
+ *                 want the stderr pipe
+ * Output: in -> the stdin FILE of the sub process
+ *         out -> the stdout FILE of the sub process
+ *         err -> the stderr FILE of the sub process
+ *         pid -> the pid of the sub process
+ * Returns: NERR_SYSTEM, NERR_IO
+ */
 NEOERR *filter_create_fp(char *cmd, FILE **in, FILE **out, FILE **err, pid_t *pid);
 
 __END_DECLS
diff -Nbru clearsilver-0.9.3/util/neo_err.c clearsilver-0.9.6/util/neo_err.c
--- clearsilver-0.9.3/util/neo_err.c	Wed Apr  2 15:07:36 2003
+++ clearsilver-0.9.6/util/neo_err.c	Tue Sep 16 16:37:42 2003
@@ -37,11 +37,18 @@
 static ULIST *Errors = NULL;
 static int Inited = 0;
 
+/* Set this to 1 to enable non-thread safe re-use of NEOERR data
+ * structures.  This was a premature performance optimization that isn't
+ * thread safe, if we want it thread safe we need to add mutex code...
+ * which has its own performance penalties...
+ */
+static int UseFreeList = 0;
+
 static NEOERR *_err_alloc(void)
 {
   NEOERR *err;
 
-  if (FreeList == NULL)
+  if (!UseFreeList || FreeList == NULL)
   {
     err = (NEOERR *)calloc (1, sizeof (NEOERR));
     if (err == NULL)
@@ -67,10 +74,17 @@
     return 0;
   if (err->next != NULL)
     _err_free(err->next);
+  if (UseFreeList)
+  {
   err->next = FreeList;
   FreeList = err;
   err->flags = 0;
   err->desc[0] = '\0';
+  }
+  else
+  {
+    free(err);
+  }
   return 0;
 }
 
diff -Nbru clearsilver-0.9.3/util/neo_err.h clearsilver-0.9.6/util/neo_err.h
--- clearsilver-0.9.3/util/neo_err.h	Wed Apr  2 15:07:36 2003
+++ clearsilver-0.9.6/util/neo_err.h	Wed Sep 24 16:50:39 2003
@@ -109,7 +109,7 @@
  */
 void nerr_log_error (NEOERR *err);
 
-#include "neo_str.h"
+#include "util/neo_str.h"
 void nerr_error_string (NEOERR *err, STRING *str);
 void nerr_error_traceback (NEOERR *err, STRING *str);
 
diff -Nbru clearsilver-0.9.3/util/neo_hdf.c clearsilver-0.9.6/util/neo_hdf.c
--- clearsilver-0.9.3/util/neo_hdf.c	Tue Aug 26 17:55:49 2003
+++ clearsilver-0.9.6/util/neo_hdf.c	Mon Oct 20 14:18:58 2003
@@ -17,6 +17,7 @@
 #include 
 #include 
 #include 
+#include 
 #include 
 #include "neo_misc.h"
 #include "neo_err.h"
@@ -298,6 +299,33 @@
   return defval;
 }
 
+char* hdf_get_valuevf (HDF *hdf, char *namefmt, va_list ap) 
+{
+  HDF *node;
+  char *name;
+
+  name = vsprintf_alloc(namefmt, ap);
+  if (name == NULL) return NULL;
+  if ((_walk_hdf(hdf, name, &node) == 0) && (node->value != NULL))
+  {
+    free(name);
+    return node->value;
+  }
+  free(name);
+  return NULL;
+}
+
+char* hdf_get_valuef (HDF *hdf, char *namefmt, ...)
+{
+  char *val;
+  va_list ap;
+
+  va_start(ap, namefmt);
+  val = hdf_get_valuevf(hdf, namefmt, ap);
+  va_end(ap);
+  return val;
+}
+
 NEOERR* hdf_get_copy (HDF *hdf, char *name, char **value, char *defval)
 {
   HDF *node;
@@ -773,6 +801,41 @@
     return nerr_pass(_set_value (hdf, dest, node->value, 0, 0, 0, NULL, NULL));
   }
   return nerr_raise (NERR_NOT_FOUND, "Unable to find %s", src);
+}
+
+NEOERR* hdf_set_valuevf (HDF *hdf, char *fmt, va_list ap)
+{
+  NEOERR *err;
+  char *k;
+  char *v;
+
+  k = vsprintf_alloc(fmt, ap);
+  if (k == NULL)
+  {
+    return nerr_raise(NERR_NOMEM, "Unable to allocate memory for format string");
+  }
+  v = strchr(k, '=');
+  if (v == NULL)
+  {
+    err = nerr_raise(NERR_ASSERT, "No equals found: %s", k);
+    free(k);
+    return err;
+  }
+  *v++ = '\0';
+  err = hdf_set_value(hdf, k, v);
+  free(k);
+  return nerr_pass(err);
+}
+
+NEOERR* hdf_set_valuef (HDF *hdf, char *fmt, ...)
+{
+  NEOERR *err;
+  va_list ap;
+
+  va_start(ap, fmt);
+  err = hdf_set_valuevf(hdf, fmt, ap);
+  va_end(ap);
+  return nerr_pass(err);
 }
 
 NEOERR* hdf_get_node (HDF *hdf, char *name, HDF **ret)
diff -Nbru clearsilver-0.9.3/util/neo_hdf.h clearsilver-0.9.6/util/neo_hdf.h
--- clearsilver-0.9.3/util/neo_hdf.h	Mon Aug 18 13:40:03 2003
+++ clearsilver-0.9.6/util/neo_hdf.h	Mon Oct 20 14:18:59 2003
@@ -14,9 +14,8 @@
 __BEGIN_DECLS
 
 #include 
-#include "neo_err.h"
-#include "neo_hash.h"
-#include "ulist.h"
+#include "util/neo_err.h"
+#include "util/neo_hash.h"
 
 #define FORCE_HASH_AT 10
 
@@ -50,62 +49,491 @@
   struct _hdf *last_child;
 } HDF;
 
+/*
+ * Function: hdf_init - Initialize an HDF data set
+ * Description: hdf_init initializes an HDF data set and returns the
+ *              pointer to the top node in the data set. 
+ * Input: hdf - pointer to an HDF pointer
+ * Output: hdf - allocated hdf node
+ * Returns: NERR_NOMEM - unable to allocate memory for dataset
+ */
 NEOERR* hdf_init (HDF **hdf);
+
+/*
+ * Function: hdf_destroy - deallocate an HDF data set
+ * Description: hdf_destroy is used to deallocate all memory associated
+ *              with an hdf data set.  Although you can pass an HDF node
+ *              as an argument to this function, you are likely to cause
+ *              a segfault if you continue to access the data set.  In
+ *              the future, we may restrict hdf_destroy so it only works
+ *              on the top level node.
+ * Input: hdf - pointer to an HDF data set allocated with hdf_init
+ * Output: None
+ * Returns: None
+ */
 void hdf_destroy (HDF **hdf);
 
+/*
+ * Function: hdf_get_int_value - Return the integer value of a point in
+ *           the data set
+ * Description: hdf_get_int_value walks the HDF data set pointed to by
+ *              hdf to name, and returns the value of that node
+ *              converted to an integer.  If that node does not exist,
+ *              or it does not contain a number, the defval is returned.  
+ * Input: hdf -> a node in an HDF data set
+ *        name -> the name of a node to walk to in the data set
+ *        defval -> value to return in case of error or if the node
+ *                  doesn't exist
+ * Output: None
+ * Returns: The integer value of the node, or the defval
+ */
 int hdf_get_int_value (HDF *hdf, char *name, int defval);
+
+/*
+ * Function: hdf_get_value - Return the value of a node in the data set
+ * Description: hdf_get_value walks the data set pointed to by hdf via
+ *              name and returns the string value located there, or
+ *              defval if the node doesn't exist
+ * Input: hdf -> the dataset node to start from
+ *        name -> the name to walk the data set to
+ *        defval -> the default value to return if the node doesn't
+ *                  exist
+ * Output: None
+ * Returns: A pointer to the string stored in the data set, or defval.
+ *          The data set maintains ownership of the string, if you want
+ *          a copy you either have to call strdup yourself, or use
+ *          hdf_get_copy
+ */
 char *hdf_get_value (HDF *hdf, char *name, char *defval);
+
+/*
+ * Function: hdf_get_valuevf - Return the value of a node in the data set
+ * Description: hdf_get_valuevf walks the data set pointed to by hdf via
+ *              namefmt printf expanded with varargs ap, and returns the
+ *              string value located there, or NULL if it doesn't exist.
+ *              This differs from hdf_get_value in that there is no
+ *              default value possible.
+ * Input: hdf -> the dataset node to start from
+ *        namefmt -> the format string
+ *        ap -> va_list of varargs
+ * Output: None
+ * Returns: A pointer to the string stored in the data set, or NULL.
+ *          The data set maintains ownership of the string, if you want
+ *          a copy you either have to call strdup yourself.
+ */
+char* hdf_get_valuevf (HDF *hdf, char *namefmt, va_list ap);
+
+/*
+ * Function: hdf_get_valuef - Return the value of a node in the data set
+ * Description: hdf_get_valuef walks the data set pointed to by hdf via
+ *              namefmt printf expanded with varargs, and returns the
+ *              string value located there, or NULL if it doesn't exist.
+ *              This differs from hdf_get_value in that there is no
+ *              default value possible.
+ * Input: hdf -> the dataset node to start from
+ *        namefmt -> the printf-style format string
+ *        ... -> arguments to fill out namefmt 
+ * Output: None
+ * Returns: A pointer to the string stored in the data set, or NULL.
+ *          The data set maintains ownership of the string, if you want
+ *          a copy you either have to call strdup yourself.
+ */
+char* hdf_get_valuef (HDF *hdf, char *namefmt, ...);
+
+/*
+ * Function: hdf_get_copy - Returns a copy of a string in the HDF data set
+ * Description: hdf_get_copy is similar to hdf_get_value, except that it
+ *              returns an malloc'd copy of the string.
+ * Input: hdf -> the dataset node to start from
+ *        name -> the name to walk the data set to
+ *        defval -> the default value to return if the node doesn't
+ *                  exist
+ * Output: value -> the allocated string (if defval = NULL, then value
+ *                  will be NULL if defval is used)
+ * Returns: NERR_NOMEM if unable to allocate the new copy 
+ */
 NEOERR* hdf_get_copy (HDF *hdf, char *name, char **value, char *defval);
 
+/*
+ * Function: hdf_get_obj - return the HDF data set node at a named location
+ * Description: hdf_get_obj walks the dataset given by hdf to the node
+ *              named name, and then returns the pointer to that node
+ * Input: hdf -> the dataset node to start from
+ *        name -> the name to walk to
+ * Output: None
+ * Returns: the pointer to the named node, or NULL if it doesn't exist
+ */
 HDF* hdf_get_obj (HDF *hdf, char *name);
-/* Always returns the node (except on NOMEM error).  Creates if
- * necessary */
+
+/*
+ * Function: hdf_get_node - Similar to hdf_get_obj except all the nodes
+ *           are created if the don't exist.
+ * Description: hdf_get_node is similar to hdf_get_obj, except instead
+ *              of stopping if it can't find a node in the tree, it will
+ *              create all of the nodes necessary to hand you back the
+ *              node you ask for.  Nodes are created with no value.
+ * Input: hdf -> the dataset node to start from
+ *        name -> the name to walk to
+ * Output: ret -> the dataset node you asked for
+ * Returns: NERR_NOMEM - unable to allocate new nodes
+ */
 NEOERR * hdf_get_node (HDF *hdf, char *name, HDF **ret);
+
+/*
+ * Function: hdf_get_child - return the first child of the named node
+ * Description: hdf_get_child will walk the dataset starting at hdf to
+ *              name, and return the first child of that node
+ * Input: hdf -> the dataset node to start from
+ *        name -> the name to walk to
+ * Output: None
+ * Returns: The first child of the named dataset node or NULL if the
+ *          node is not found (or it has no children)
+ */
 HDF* hdf_get_child (HDF *hdf, char *name);
+
+/*
+ * Function: hdf_get_attr -
+ * Description:
+ * Input:
+ * Output:
+ * Returns:
+ */
 HDF_ATTR* hdf_get_attr (HDF *hdf, char *name);
+
+/*
+ * Function: hdf_set_attr -
+ * Description:
+ * Input:
+ * Output:
+ * Returns:
+ */
 NEOERR* hdf_set_attr (HDF *hdf, char *name, char *key, char *value);
+
+/*
+ * Function: hdf_obj_child - Return the first child of a dataset node
+ * Description: hdf_obj_child and the other hdf_obj_ functions are
+ *              accessors to the HDF dataset.  Although we do not
+ *              currently "hide" the HDF struct implementation, we
+ *              recommend you use the accessor functions instead of
+ *              accessing the values directly.
+ * Input: hdf -> the hdf dataset node
+ * Output: None
+ * Returns: The pointer to the first child, or NULL if there is none
+ */
 HDF* hdf_obj_child (HDF *hdf);
+
+/*
+ * Function: hdf_obj_next - Return the next node of a dataset level
+ * Description: hdf_obj_next is an accessor function for the HDF struct
+ * Input: hdf -> the hdf dataset node
+ * Output: None
+ * Returns: The pointer to the next node, or NULL if there is none
+ */
 HDF* hdf_obj_next (HDF *hdf);
+
+/*
+ * Function: hdf_obj_top - Return the pointer to the top dataset node
+ * Description: hdf_obj_top is an accessor function which returns a
+ *              pointer to the top of the dataset, the node which was
+ *              returned by hdf_init.  This is most useful for
+ *              implementations of language wrappers where individual
+ *              nodes are tied garbage colletion wise to the top node of
+ *              the data set
+ * Input: hdf -> the hdf dataset node
+ * Output: None
+ * Returns: The pointer to the top node
+ */
 HDF* hdf_obj_top (HDF *hdf);
+
+/*
+ * Function: hdf_obj_attr - Return the HDF Attributes for a node
+ * Description:
+ * Input:
+ * Output:
+ * Returns:
+ */
 HDF_ATTR* hdf_obj_attr (HDF *hdf);
+
+/*
+ * Function: hdf_obj_name - Return the name of a node
+ * Description: hdf_obj_name is an accessor function for a datset node
+ *              which returns the name of the node.  This is just the
+ *              local name, and not the full path.
+ * Input: hdf -> the hdf dataset node
+ * Output: None
+ * Returns: The name of the node.  If this is the top node, the name is
+ * NULL.
+ */
 char* hdf_obj_name (HDF *hdf);
+
+/*
+ * Function: hdf_obj_value - Return the value of a node
+ * Description: hdf_obj_value is an accessor function for a dataset node
+ *              which returns the value of the node, or NULL if the node
+ *              has no value.  This is not a copy of the value, so the
+ *              node retains ownership of the value
+ * Input: hdf -> the hdf dataset node
+ * Output: None
+ * Returns: The value of the node, or NULL if it has no value
+ */
 char* hdf_obj_value (HDF *hdf);
 
+/*
+ * Function: hdf_set_value - Set the value of a named node
+ * Description: hdf_set_value will set the value of a named node.  All
+ *              of the interstitial nodes which don't exist will be
+ *              created with a value of NULL.  Existing nodes are not
+ *              modified.  New nodes are created at the end of the list.
+ *              If a list of nodes exceeds FORCE_HASH_AT, then a HASH
+ *              will be created at that level and all of the nodes will
+ *              be added to the hash for faster lookup times.
+ *              The copy of the value will be made which the dataset
+ *              will own.
+ * Input: hdf -> the pointer to the hdf dataset
+ *        name -> the named node to walk to
+ *        value -> the value to set the node to
+ * Output: None
+ * Returns: NERR_NOMEM
+ */
 NEOERR* hdf_set_value (HDF *hdf, char *name, char *value);
+
+/*
+ * Function: hdf_set_valuef - Set the value of a named node
+ * Description: hdf_set_valuef is a convenience function that wraps
+ *              hdf_set_value.  Due to limitations of C, the fmt is in
+ *              the format "name=value", where we will first format the
+ *              entire string, and then break it at the first (from the
+ *              left) equal sign (=) and use the left portion as the
+ *              name and the right portion as the value.  This function
+ *              is somewhat inefficient in that it first allocates the
+ *              full name=value, and then the call to hdf_set_value
+ *              duplicates the value portion, and then we free the
+ *              name=value.
+ *              Currently, we don't strip whitespace from the key or
+ *              value.  In the future, this function might work more
+ *              like reading a single line of an HDF string or file,
+ *              allowing for attributes and symlinks to be specified...
+ *              maybe.
+ * Input: hdf -> the pointer to the hdf dataset
+ *        fmt -> the name=value printf(3) format string
+ * Output: None
+ * Returns: NERR_NOMEM
+ */
+NEOERR* hdf_set_valuef (HDF *hdf, char *fmt, ...);
+NEOERR* hdf_set_valuevf (HDF *hdf, char *fmt, va_list ap); 
+
+/*
+ * Function: hdf_set_int_value - Set the value of a named node to a number
+ * Description: hdf_set_int_value is a helper function that maps an
+ *              integer to a string, and then calls hdf_set_value with
+ *              that string
+ * Input: hdf -> the pointer to the hdf dataset
+ *        name -> the named node to walk to
+ *        value -> the value to set the node to
+ * Output: None
+ * Returns: NERR_NOMEM
+ */
 NEOERR* hdf_set_int_value (HDF *hdf, char *name, int value);
+
+/*
+ * Function: hdf_set_copy -> Copy a value from one location in the
+ *           dataset to another
+ * Description: hdf_set_copy first walks the hdf dataset to the named src
+ *              node, and then copies that value to the named dest node.
+ *              If the src node is not found, an error is raised.
+ * Input: hdf -> the pointer to the dataset node
+ *        dest -> the name of the destination node
+ *        src -> the name of the source node
+ * Output: None
+ * Returns: NERR_NOMEM, NERR_NOT_FOUND
+ */
 NEOERR* hdf_set_copy (HDF *hdf, char *dest, char *src);
+
+/*
+ * Function: hdf_set_buf - Set the value of a node without duplicating
+ *           the value
+ * Description: hdf_set_buf is similar to hdf_set_value, except the
+ *              dataset takes ownership of the value instead of making a
+ *              copy of it.  The dataset assumes that value was
+ *              malloc'd, since it will attempt to free it when
+ *              hdf_destroy is called
+ * Input: hdf -> the hdf dataset node
+ *        name -> the name to walk to
+ *        value -> the malloc'd value
+ * Output: None
+ * Returns: NERR_NOMEM - unable to allocate a node
+ */
+
 NEOERR* hdf_set_buf (HDF *hdf, char *name, char *value);
 
+/*
+ * Function: hdf_set_symlink - Set part of the tree to link to another
+ * Description: hdf_set_symlink creates a link between two sections of
+ *              an HDF dataset.  The link is "by name" hence the term
+ *              "symlink".  This means that the destination node does
+ *              not need to exist.  Any attempt to access the source
+ *              node will cause the function to walk to the dest node,
+ *              and then continue walking from there.  Using symlinks
+ *              can "hide" values in the dataset since you won't be able
+ *              to access any children of the linked node directly,
+ *              though dumps and other things which access the data
+ *              structure directly will bypass the symlink.  Use this
+ *              feature sparingly as its likely to surprise you.
+ * Input: hdf -> the dataset node
+ *        src -> the source node name 
+ *        dest -> the destination node name (from the top of the
+ *        dataset, not relative names)
+ * Output: None
+ * Returns: NERR_NOMEM
+ */
 NEOERR *hdf_set_symlink (HDF *hdf, char *src, char *dest);
 
 /*
  * Function: hdf_sort_obj - sort the children of an HDF node 
  * Description: hdf_sort_obj will sort the children of an HDF node,
  *              based on the given comparison function.
+ *              This function works by creating an array of the pointers
+ *              for each child object of h, using qsort to sort that
+ *              array, and then re-ordering the linked list of children
+ *              to the new order.  The qsort compare function uses a
+ *              pointer to the value in the array, which in our case is
+ *              a pointer to an HDF struct, so your comparison function
+ *              should work on HDF ** pointers.
  * Input: h - HDF node
  *        compareFunc - function which returns 1,0,-1 depending on some 
- *                      criteria.  Given two children of h
+ *                      criteria.  The arguments to this sort function
+ *                      are pointers to pointers to HDF elements.  For
+ *                      example:
+ *                      int sortByName(const void *a, const void *b) {
+ *                        HDF **ha = (HDF **)a;
+ *                        HDF **hb = (HDF **)b;
+ *
+ *                      return strcasecmp(hdf_obj_name(*ha), hdf_obj_name(*hb));
+ *                      }
+ *
  * Output: None (h children will be sorted)
- * Return: None
+ * Return: NERR_NOMEM 
  */
 NEOERR *hdf_sort_obj(HDF *h, int (*compareFunc)(const void *, const void *));
 
+/*
+ * Function: hdf_read_file - read an HDF data file
+ * Description:
+ * Input:
+ * Output:
+ * Returns: NERR_IO, NERR_NOMEM, NERR_PARSE
+ */
 NEOERR* hdf_read_file (HDF *hdf, char *path);
+
+/*
+ * Function: hdf_write_file - write an HDF data file
+ * Description:
+ * Input:
+ * Output:
+ * Returns: NERR_IO
+ */
 NEOERR* hdf_write_file (HDF *hdf, char *path);
+
+/*
+ * Function: hdf_write_file_atomic - write an HDF data file atomically
+ * Description: hdf_write_file_atomic is similar to hdf_write_file,
+ *              except the new file is created with a unique name and
+ *              then rename(2) is used to atomically replace the old
+ *              file with the new file
+ * Input:
+ * Output:
+ * Returns: NERR_IO
+ */
 NEOERR* hdf_write_file_atomic (HDF *hdf, char *path);
 
+/*
+ * Function: hdf_read_string - read an HDF string
+ * Description:
+ * Input:
+ * Output:
+ * Returns: NERR_NOMEM, NERR_PARSE
+ */
 NEOERR* hdf_read_string (HDF *hdf, char *s);
+
+/*
+ * Function: hdf_read_string_ignore - Read an HDF string and ignore errors
+ * Description:
+ * Input:
+ * Output:
+ * Returns: NERR_NOMEM
+ */
 NEOERR* hdf_read_string_ignore (HDF *hdf, char *s, int ignore);
+
+/*
+ * Function: hdf_write_string - serialize an HDF dataset to a string
+ * Description:
+ * Input:
+ * Output:
+ * Returns: NERR_NOMEM
+ */
 NEOERR* hdf_write_string (HDF *hdf, char **s);
 
+/*
+ * Function: hdf_dump - dump an HDF dataset to stdout
+ * Description:
+ * Input:
+ * Output:
+ * Returns:
+ */
 NEOERR* hdf_dump (HDF *hdf, char *prefix);
+
+/*
+ * Function: hdf_dump_format - dump an HDF dataset to FILE *fp
+ * Description:
+ * Input:
+ * Output:
+ * Returns:
+ */
 NEOERR* hdf_dump_format (HDF *hdf, int lvl, FILE *fp);
+
+/*
+ * Function: hdf_dump_str - dump an HDF dataset to STRING
+ * Description:
+ * Input:
+ * Output:
+ * Returns:
+ */
 NEOERR* hdf_dump_str(HDF *hdf, char *prefix, int compact, STRING *str);
 
+/*
+ * Function: hdf_remove_tree - delete a subtree of an HDF dataset
+ * Description:
+ * Input:
+ * Output:
+ * Returns:
+ */
 NEOERR* hdf_remove_tree (HDF *hdf, char *name);
+
+/*
+ * Function: hdf_copy - copy part of an HDF dataset to another
+ * Description: hdf_copy is a deep copy of an HDF tree pointed to by
+ *              src to the named node of dest.  dest and src need not be
+ *              part of the same data set
+ * Input: dest_hdf -> the destination dataset
+ *        name -> the name of the destination node
+ *        src -> the hdf dataset to copy to the destination
+ * Output: None
+ * Returns: NERR_NOMEM, NERR_NOT_FOUND
+ */
 NEOERR* hdf_copy (HDF *dest_hdf, char *name, HDF *src);
 
-/* Utility function */
+/*
+ * Function: hdf_search_path - Find a file given a search path in HDF
+ * Description: hdf_search_path is a convenience/utility function that
+ *              searches for relative filenames in a search path.  The
+ *              search path is the list given by the children of
+ *              hdf.loadpaths.
+ * Input: hdf -> the hdf dataset to use
+ *        path -> the relative path
+ *        full -> a pointer to a _POSIX_PATH_MAX buffer
+ * Output: full -> the full path of the file
+ * Returns: NERR_NOT_FOUND if the file wasn't found in the search path
+ */
 NEOERR* hdf_search_path (HDF *hdf, char *path, char *full);
 
 __END_DECLS
diff -Nbru clearsilver-0.9.3/util/neo_str.c clearsilver-0.9.6/util/neo_str.c
--- clearsilver-0.9.3/util/neo_str.c	Wed Apr  2 19:03:01 2003
+++ clearsilver-0.9.6/util/neo_str.c	Wed Oct  8 16:43:02 2003
@@ -140,7 +140,7 @@
   return STATUS_OK;
 }
 
-/* currently, this requires a C99 compliant snprintf */
+/* this is much more efficient with C99 snprintfs... */
 NEOERR *string_appendvf (STRING *str, char *fmt, va_list ap) 
 {
   NEOERR *err;
@@ -151,14 +151,29 @@
   va_copy(tmp, ap);
   /* determine length */
   size = sizeof (buf);
-  bl = vsnprintf (buf, size, fmt, ap);
+  bl = vsnprintf (buf, size, fmt, tmp);
   if (bl > -1 && bl < size)
     return string_appendn (str, buf, bl);
 
+  /* Handle non-C99 snprintfs (requires extra malloc/free and copy) */
+  if (bl == -1)
+  {
+    char *a_buf;
+
+    va_copy(tmp, ap);
+    a_buf = vnsprintf_alloc(size*2, fmt, tmp);
+    if (a_buf == NULL)
+      return nerr_raise(NERR_NOMEM, 
+	  "Unable to allocate memory for formatted string");
+    err = string_append(str, a_buf);
+    free(a_buf);
+    return nerr_pass(err);
+  }
+
   err = string_check_length (str, bl+1);
   if (err != STATUS_OK) return nerr_pass (err);
-  va_copy(ap, tmp);
-  vsprintf (str->buf + str->len, fmt, ap);
+  va_copy(tmp, ap);
+  vsprintf (str->buf + str->len, fmt, tmp);
   str->len += bl;
   str->buf[str->len] = '\0';
 
@@ -252,12 +267,38 @@
   arr->count = 0;
 }
 
+/* Mostly used by vprintf_alloc for non-C99 compliant snprintfs,
+ * this is like vsprintf_alloc except it takes a "suggested" size */
+char *vnsprintf_alloc (int start_size, char *fmt, va_list ap)
+{
+  char *b = NULL;
+  int bl, size;
+  va_list tmp;
+
+  size = start_size;
+
+  b = (char *) malloc (size * sizeof(char));
+  if (b == NULL) return NULL;
+  while (1)
+  {
+    va_copy(tmp, ap);
+    bl = vsnprintf (b, size, fmt, tmp);
+    if (bl > -1 && bl < size)
+      return b;
+    if (bl > -1)
+      size = bl + 1;
+    else
+      size *= 2;
+    b = (char *) realloc (b, size * sizeof(char));
+    if (b == NULL) return NULL;
+  }
+}
+
 /* This works better with a C99 compliant vsnprintf, but should work ok
  * with versions that return a -1 if it overflows the buffer */
 char *vsprintf_alloc (char *fmt, va_list ap)
 {
   char buf[4096];
-  char *b = NULL;
   int bl, size;
   va_list tmp;
 
@@ -266,7 +307,7 @@
   va_copy(tmp, ap);
   
   size = sizeof (buf);
-  bl = vsnprintf (buf, sizeof (buf), fmt, ap);
+  bl = vsnprintf (buf, sizeof (buf), fmt, tmp);
   if (bl > -1 && bl < size)
     return strdup (buf);
 
@@ -275,20 +316,10 @@
   else
     size *= 2;
 
-  b = (char *) malloc (size * sizeof(char));
-  if (b == NULL) return NULL;
-  while (1)
-  {
-    va_copy(ap, tmp);
-    bl = vsnprintf (b, size, fmt, ap);
-    if (bl > -1 && bl < size)
-      return b;
-    size *= 2;
-    b = (char *) realloc (b, size * sizeof(char));
-    if (b == NULL) return NULL;
-  }
+  return vnsprintf_alloc(size, fmt, ap);
 }
 
+
 char *sprintf_alloc (char *fmt, ...)
 {
   va_list ap;
@@ -296,6 +327,23 @@
 
   va_start (ap, fmt);
   r = vsprintf_alloc (fmt, ap);
+  va_end (ap);
+  return r;
+}
+
+/* This is mostly just here for completeness, I doubt anyone would use
+ * this (its more efficient (time-wise) if start_size is bigger than the
+ * resulting string.  Its less efficient than sprintf_alloc if we have a
+ * C99 snprintf and it doesn't fit in start_size. 
+ * BTW: If you are really worried about the efficiency of these
+ * functions, maybe you shouldn't be using them in the first place... */
+char *nsprintf_alloc (int start_size, char *fmt, ...)
+{
+  va_list ap;
+  char *r;
+
+  va_start (ap, fmt);
+  r = vnsprintf_alloc (start_size, fmt, ap);
   va_end (ap);
   return r;
 }
diff -Nbru clearsilver-0.9.3/util/neo_str.h clearsilver-0.9.6/util/neo_str.h
--- clearsilver-0.9.3/util/neo_str.h	Fri Aug 30 17:39:46 2002
+++ clearsilver-0.9.6/util/neo_str.h	Wed Oct  8 16:43:02 2003
@@ -27,7 +27,9 @@
 void neos_lower (char *s);
 
 char *sprintf_alloc (char *fmt, ...);
+char *nsprintf_alloc (int start_size, char *fmt, ...);
 char *vsprintf_alloc (char *fmt, va_list ap);
+char *vnsprintf_alloc (int start_size, char *fmt, va_list ap);
 
 typedef struct _string
 {
@@ -58,7 +60,7 @@
 void string_clear (STRING *str);
 
 /* typedef struct _ulist ULIST; */
-#include "ulist.h"
+#include "util/ulist.h"
 NEOERR *string_array_split (ULIST **list, char *s, char *sep, int max);
 
 BOOL reg_search (char *re, char *str);
diff -Nbru clearsilver-0.9.3/util/skiplist.h clearsilver-0.9.6/util/skiplist.h
--- clearsilver-0.9.3/util/skiplist.h	Wed Apr  2 15:07:36 2003
+++ clearsilver-0.9.6/util/skiplist.h	Wed Sep 24 16:50:39 2003
@@ -17,7 +17,7 @@
 #ifndef __SKIPLIST_H_
 #define __SKIPLIST_H_
 
-#include 
+#include "util/neo_err.h"
 
 __BEGIN_DECLS
 
diff -Nbru clearsilver-0.9.3/util/test/Makefile clearsilver-0.9.6/util/test/Makefile
--- clearsilver-0.9.3/util/test/Makefile	Mon Mar 31 17:45:55 2003
+++ clearsilver-0.9.6/util/test/Makefile	Wed Oct  8 16:46:26 2003
@@ -10,6 +10,10 @@
 HDFTEST_SRC = hdftest.c
 HDFTEST_OBJ = $(HDFTEST_SRC:%.c=%.o)
 
+HDFSORTTEST_EXE = hdf_sort_test
+HDFSORTTEST_SRC = hdf_sort_test.c
+HDFSORTTEST_OBJ = $(HDFSORTTEST_SRC:%.c=%.o)
+
 HDFLOADTEST_EXE = hdfloadtest
 HDFLOADTEST_SRC = hdfloadtest.c
 HDFLOADTEST_OBJ = $(HDFLOADTEST_SRC:%.c=%.o)
@@ -38,6 +42,7 @@
 LIBS += -L$(LIB_DIR) -lneo_utl 
 
 TARGETS = $(HDFTEST_EXE) $(LISTDIRTEST_EXE) $(HDFCOPYTEST_EXE) \
+	$(HDFSORTTEST_EXE) \
 	$(HDFLOADTEST_EXE) $(NETTEST_EXE) $(DATETEST_EXE) \
 	$(HASHTEST_EXE)
 
@@ -45,6 +50,9 @@
 
 $(HDFTEST_EXE): $(HDFTEST_OBJ) $(NTR_LIB)
 	$(LD) $@ $(HDFTEST_OBJ) $(LIBS)
+
+$(HDFSORTTEST_EXE): $(HDFSORTTEST_OBJ) $(NTR_LIB)
+	$(LD) $@ $(HDFSORTTEST_OBJ) $(LIBS)
 
 $(HDFLOADTEST_EXE): $(HDFLOADTEST_OBJ) $(NTR_LIB)
 	$(LD) $@ $(HDFLOADTEST_OBJ) $(LIBS) # -lefence
diff -Nbru clearsilver-0.9.3/util/test/hdf_sort_test.c clearsilver-0.9.6/util/test/hdf_sort_test.c
--- clearsilver-0.9.3/util/test/hdf_sort_test.c	Wed Dec 31 16:00:00 1969
+++ clearsilver-0.9.6/util/test/hdf_sort_test.c	Wed Oct  8 16:46:26 2003
@@ -0,0 +1,102 @@
+
+#include "cs_config.h"
+#include 
+#include 
+#include "util/neo_misc.h"
+#include "util/neo_hdf.h"
+#include "util/neo_rand.h"
+
+NEOERR* hdf_set_valuef(HDF *hdf, char *namefmt, char *valuefmt, ...)
+{
+  char newstr[2048];
+  char one[2];
+  va_list ap;
+  char *nametok;
+  char *valtok;
+
+  char newfmt[2048];
+
+
+  one[0]=(char)1;
+  one[1]=(char)0;
+
+  snprintf(newfmt,2048,"%s%s%s",namefmt,one,valuefmt);
+
+  va_start(ap, valuefmt);
+  vsnprintf (newstr, 1024, newfmt, ap);
+  va_end(ap);
+
+  /* split it at 1 */
+  nametok=strtok(newstr,one);
+  valtok=strtok(NULL,one);
+
+
+  return hdf_set_value(hdf,nametok,valtok);
+}
+
+
+int TestCompare(const void* pa, const void* pb)
+{
+  HDF **a = (HDF **)pa;
+  HDF **b = (HDF **)pb;
+  float aVal,bVal;
+
+  aVal = atof(hdf_get_value(*a,"val","0"));
+  bVal = atof(hdf_get_value(*b,"val","0"));
+
+  printf("TestCompare aVal=%f [%s]  bVal=%f [%s]\n",aVal,hdf_get_value(*a,"name","?"),bVal,hdf_get_value(*b,"name","?"));
+
+  if (aVal
 
 typedef struct _column