1/*
2 * Copyright 2001-2004 Brandon Long
3 * All Rights Reserved.
4 *
5 * ClearSilver Templating System
6 *
7 * This code is made available under the terms of the ClearSilver License.
8 * http://www.clearsilver.net/license.hdf
9 *
10 */
11
12#include "cs_config.h"
13
14#include <unistd.h>
15#include <string.h>
16#include <stdlib.h>
17#include <ctype.h>
18#include <stdio.h>
19#include <stdarg.h>
20#include <time.h>
21#if defined(HTML_COMPRESSION)
22#include <zlib.h>
23#endif
24
25#include "util/neo_misc.h"
26#include "util/neo_err.h"
27#include "util/neo_hdf.h"
28#include "util/neo_str.h"
29#include "cgi.h"
30#include "cgiwrap.h"
31#include "html.h"
32#include "cs/cs.h"
33
34
35struct _cgi_vars
36{
37  char *env_name;
38  char *hdf_name;
39} CGIVars[] = {
40  {"AUTH_TYPE", "AuthType"},
41  {"CONTENT_TYPE", "ContentType"},
42  {"CONTENT_LENGTH", "ContentLength"},
43  {"DOCUMENT_ROOT", "DocumentRoot"},
44  {"GATEWAY_INTERFACE", "GatewayInterface"},
45  {"PATH_INFO", "PathInfo"},
46  {"PATH_TRANSLATED", "PathTranslated"},
47  {"QUERY_STRING", "QueryString"},
48  {"REDIRECT_REQUEST", "RedirectRequest"},
49  {"REDIRECT_QUERY_STRING", "RedirectQueryString"},
50  {"REDIRECT_STATUS", "RedirectStatus"},
51  {"REDIRECT_URL", "RedirectURL",},
52  {"REMOTE_ADDR", "RemoteAddress"},
53  {"REMOTE_HOST", "RemoteHost"},
54  {"REMOTE_IDENT", "RemoteIdent"},
55  {"REMOTE_PORT", "RemotePort"},
56  {"REMOTE_USER", "RemoteUser"},
57  {"REMOTE_GROUP", "RemoteGroup"},
58  {"REQUEST_METHOD", "RequestMethod"},
59  {"REQUEST_URI", "RequestURI"},
60  {"SCRIPT_FILENAME", "ScriptFilename"},
61  {"SCRIPT_NAME", "ScriptName"},
62  {"SERVER_ADDR", "ServerAddress"},
63  {"SERVER_ADMIN", "ServerAdmin"},
64  {"SERVER_NAME", "ServerName"},
65  {"SERVER_PORT", "ServerPort"},
66  {"SERVER_ROOT", "ServerRoot"},
67  {"SERVER_PROTOCOL", "ServerProtocol"},
68  {"SERVER_SOFTWARE", "ServerSoftware"},
69  /* SSL Vars from mod_ssl */
70  {"HTTPS", "HTTPS"},
71  {"SSL_PROTOCOL", "SSL.Protocol"},
72  {"SSL_SESSION_ID", "SSL.SessionID"},
73  {"SSL_CIPHER", "SSL.Cipher"},
74  {"SSL_CIPHER_EXPORT", "SSL.Cipher.Export"},
75  {"SSL_CIPHER_USEKEYSIZE", "SSL.Cipher.UseKeySize"},
76  {"SSL_CIPHER_ALGKEYSIZE", "SSL.Cipher.AlgKeySize"},
77  {"SSL_VERSION_INTERFACE", "SSL.Version.Interface"},
78  {"SSL_VERSION_LIBRARY", "SSL.Version.Library"},
79  {"SSL_CLIENT_M_VERSION", "SSL.Client.M.Version"},
80  {"SSL_CLIENT_M_SERIAL", "SSL.Client.M.Serial"},
81  {"SSL_CLIENT_S_DN", "SSL.Client.S.DN"},
82  {"SSL_CLIENT_S_DN_x509", "SSL.Client.S.DN.x509"},
83  {"SSL_CLIENT_I_DN", "SSL.Client.I.DN"},
84  {"SSL_CLIENT_I_DN_x509", "SSL.Client.I.DN.x509"},
85  {"SSL_CLIENT_V_START", "SSL.Client.V.Start"},
86  {"SSL_CLIENT_V_END", "SSL.Client.V.End"},
87  {"SSL_CLIENT_A_SIG", "SSL.Client.A.SIG"},
88  {"SSL_CLIENT_A_KEY", "SSL.Client.A.KEY"},
89  {"SSL_CLIENT_CERT", "SSL.Client.CERT"},
90  {"SSL_CLIENT_CERT_CHAINn", "SSL.Client.CERT.CHAINn"},
91  {"SSL_CLIENT_VERIFY", "SSL.Client.Verify"},
92  {"SSL_SERVER_M_VERSION", "SSL.Server.M.Version"},
93  {"SSL_SERVER_M_SERIAL", "SSL.Server.M.Serial"},
94  {"SSL_SERVER_S_DN", "SSL.Server.S.DN"},
95  {"SSL_SERVER_S_DN_x509", "SSL.Server.S.DN.x509"},
96  {"SSL_SERVER_S_DN_CN", "SSL.Server.S.DN.CN"},
97  {"SSL_SERVER_S_DN_EMAIL", "SSL.Server.S.DN.Email"},
98  {"SSL_SERVER_S_DN_O", "SSL.Server.S.DN.O"},
99  {"SSL_SERVER_S_DN_OU", "SSL.Server.S.DN.OU"},
100  {"SSL_SERVER_S_DN_C", "SSL.Server.S.DN.C"},
101  {"SSL_SERVER_S_DN_SP", "SSL.Server.S.DN.SP"},
102  {"SSL_SERVER_S_DN_L", "SSL.Server.S.DN.L"},
103  {"SSL_SERVER_I_DN", "SSL.Server.I.DN"},
104  {"SSL_SERVER_I_DN_x509", "SSL.Server.I.DN.x509"},
105  {"SSL_SERVER_I_DN_CN", "SSL.Server.I.DN.CN"},
106  {"SSL_SERVER_I_DN_EMAIL", "SSL.Server.I.DN.Email"},
107  {"SSL_SERVER_I_DN_O", "SSL.Server.I.DN.O"},
108  {"SSL_SERVER_I_DN_OU", "SSL.Server.I.DN.OU"},
109  {"SSL_SERVER_I_DN_C", "SSL.Server.I.DN.C"},
110  {"SSL_SERVER_I_DN_SP", "SSL.Server.I.DN.SP"},
111  {"SSL_SERVER_I_DN_L", "SSL.Server.I.DN.L"},
112  {"SSL_SERVER_V_START", "SSL.Server.V.Start"},
113  {"SSL_SERVER_V_END", "SSL.Server.V.End"},
114  {"SSL_SERVER_A_SIG", "SSL.Server.A.SIG"},
115  {"SSL_SERVER_A_KEY", "SSL.Server.A.KEY"},
116  {"SSL_SERVER_CERT", "SSL.Server.CERT"},
117  /* SSL Vars mapped from others */
118  /* Hmm, if we're running under mod_ssl w/ +CompatEnvVars, we set these
119   * twice... */
120  {"SSL_PROTOCOL_VERSION", "SSL.Protocol"},
121  {"SSLEAY_VERSION", "SSL.Version.Library"},
122  {"HTTPS_CIPHER", "SSL.Cipher"},
123  {"HTTPS_EXPORT", "SSL.Cipher.Export"},
124  {"HTTPS_SECRETKEYSIZE", "SSL.Cipher.UseKeySize"},
125  {"HTTPS_KEYSIZE", "SSL.Cipher.AlgKeySize"},
126  {"SSL_SERVER_KEY_SIZE", "SSL.Cipher.AlgKeySize"},
127  {"SSL_SERVER_CERTIFICATE", "SSL.Server.CERT"},
128  {"SSL_SERVER_CERT_START", "SSL.Server.V.Start"},
129  {"SSL_SERVER_CERT_END", "SSL.Server.V.End"},
130  {"SSL_SERVER_CERT_SERIAL", "SSL.Server.M.Serial"},
131  {"SSL_SERVER_SIGNATURE_ALGORITHM", "SSL.Server.A.SIG"},
132  {"SSL_SERVER_DN", "SSL.Server.S.DN"},
133  {"SSL_SERVER_CN", "SSL.Server.S.DN.CN"},
134  {"SSL_SERVER_EMAIL", "SSL.Server.S.DN.Email"},
135  {"SSL_SERVER_O", "SSL.Server.S.DN.O"},
136  {"SSL_SERVER_OU", "SSL.Server.S.DN.OU"},
137  {"SSL_SERVER_C", "SSL.Server.S.DN.C"},
138  {"SSL_SERVER_SP", "SSL.Server.S.DN.SP"},
139  {"SSL_SERVER_L", "SSL.Server.S.DN.L"},
140  {"SSL_SERVER_IDN", "SSL.Server.I.DN"},
141  {"SSL_SERVER_ICN", "SSL.Server.I.DN.CN"},
142  {"SSL_SERVER_IEMAIL", "SSL.Server.I.DN.Email"},
143  {"SSL_SERVER_IO", "SSL.Server.I.DN.O"},
144  {"SSL_SERVER_IOU", "SSL.Server.I.DN.OU"},
145  {"SSL_SERVER_IC", "SSL.Server.I.DN.C"},
146  {"SSL_SERVER_ISP", "SSL.Server.I.DN.SP"},
147  {"SSL_SERVER_IL", "SSL.Server.I.DN.L"},
148  {"SSL_CLIENT_CERTIFICATE", "SSL.Client.CERT"},
149  {"SSL_CLIENT_CERT_START", "SSL.Client.V.Start"},
150  {"SSL_CLIENT_CERT_END", "SSL.Client.V.End"},
151  {"SSL_CLIENT_CERT_SERIAL", "SSL.Client.M.Serial"},
152  {"SSL_CLIENT_SIGNATURE_ALGORITHM", "SSL.Client.A.SIG"},
153  {"SSL_CLIENT_DN", "SSL.Client.S.DN"},
154  {"SSL_CLIENT_CN", "SSL.Client.S.DN.CN"},
155  {"SSL_CLIENT_EMAIL", "SSL.Client.S.DN.Email"},
156  {"SSL_CLIENT_O", "SSL.Client.S.DN.O"},
157  {"SSL_CLIENT_OU", "SSL.Client.S.DN.OU"},
158  {"SSL_CLIENT_C", "SSL.Client.S.DN.C"},
159  {"SSL_CLIENT_SP", "SSL.Client.S.DN.SP"},
160  {"SSL_CLIENT_L", "SSL.Client.S.DN.L"},
161  {"SSL_CLIENT_IDN", "SSL.Client.I.DN"},
162  {"SSL_CLIENT_ICN", "SSL.Client.I.DN.CN"},
163  {"SSL_CLIENT_IEMAIL", "SSL.Client.I.DN.Email"},
164  {"SSL_CLIENT_IO", "SSL.Client.I.DN.O"},
165  {"SSL_CLIENT_IOU", "SSL.Client.I.DN.OU"},
166  {"SSL_CLIENT_IC", "SSL.Client.I.DN.C"},
167  {"SSL_CLIENT_ISP", "SSL.Client.I.DN.SP"},
168  {"SSL_CLIENT_IL", "SSL.Client.I.DN.L"},
169  {"SSL_EXPORT", "SSL.Cipher.Export"},
170  {"SSL_KEYSIZE", "SSL.Cipher.AlgKeySize"},
171  {"SSL_SECKEYSIZE", "SSL.Cipher.UseKeySize"},
172  {"SSL_SSLEAY_VERSION", "SSL.Version.Library"},
173/* Old vars not in mod_ssl */
174  {"SSL_STRONG_CRYPTO", "SSL.Strong.Crypto"},
175  {"SSL_SERVER_KEY_EXP", "SSL.Server.Key.Exp"},
176  {"SSL_SERVER_KEY_ALGORITHM", "SSL.Server.Key.Algorithm"},
177  {"SSL_SERVER_KEY_SIZE", "SSL.Server.Key.Size"},
178  {"SSL_SERVER_SESSIONDIR", "SSL.Server.SessionDir"},
179  {"SSL_SERVER_CERTIFICATELOGDIR", "SSL.Server.CertificateLogDir"},
180  {"SSL_SERVER_CERTFILE", "SSL.Server.CertFile"},
181  {"SSL_SERVER_KEYFILE", "SSL.Server.KeyFile"},
182  {"SSL_SERVER_KEYFILETYPE", "SSL.Server.KeyFileType"},
183  {"SSL_CLIENT_KEY_EXP", "SSL.Client.Key.Exp"},
184  {"SSL_CLIENT_KEY_ALGORITHM", "SSL.Client.Key.Algorithm"},
185  {"SSL_CLIENT_KEY_SIZE", "SSL.Client.Key.Size"},
186  {NULL, NULL}
187};
188
189struct _http_vars
190{
191  char *env_name;
192  char *hdf_name;
193} HTTPVars[] = {
194  {"HTTP_ACCEPT", "Accept"},
195  {"HTTP_ACCEPT_CHARSET", "AcceptCharset"},
196  {"HTTP_ACCEPT_ENCODING", "AcceptEncoding"},
197  {"HTTP_ACCEPT_LANGUAGE", "AcceptLanguage"},
198  {"HTTP_COOKIE", "Cookie"},
199  {"HTTP_HOST", "Host"},
200  {"HTTP_USER_AGENT", "UserAgent"},
201  {"HTTP_IF_MODIFIED_SINCE", "IfModifiedSince"},
202  {"HTTP_REFERER", "Referer"},
203  {"HTTP_VIA", "Via"},
204  /* SOAP */
205  {"HTTP_SOAPACTION", "Soap.Action"},
206  {NULL, NULL}
207};
208
209static char *Argv0 = "";
210
211int IgnoreEmptyFormVars = 0;
212
213static int ExceptionsInit = 0;
214NERR_TYPE CGIFinished = -1;
215NERR_TYPE CGIUploadCancelled = -1;
216NERR_TYPE CGIParseNotHandled = -1;
217
218static NEOERR *_add_cgi_env_var (CGI *cgi, char *env, char *name)
219{
220  NEOERR *err;
221  char *s;
222
223  err = cgiwrap_getenv (env, &s);
224  if (err != STATUS_OK) return nerr_pass (err);
225  if (s != NULL)
226  {
227    err = hdf_set_buf (cgi->hdf, name, s);
228    if (err != STATUS_OK)
229    {
230      free(s);
231      return nerr_pass (err);
232    }
233  }
234  return STATUS_OK;
235}
236
237char *cgi_url_unescape (char *value)
238{
239  int i = 0, o = 0;
240  unsigned char *s = (unsigned char *)value;
241
242  if (s == NULL) return value;
243  while (s[i])
244  {
245    if (s[i] == '+')
246    {
247      s[o++] = ' ';
248      i++;
249    }
250    else if (s[i] == '%' && isxdigit(s[i+1]) && isxdigit(s[i+2]))
251    {
252      char num;
253      num = (s[i+1] >= 'A') ? ((s[i+1] & 0xdf) - 'A') + 10 : (s[i+1] - '0');
254      num *= 16;
255      num += (s[i+2] >= 'A') ? ((s[i+2] & 0xdf) - 'A') + 10 : (s[i+2] - '0');
256      s[o++] = num;
257      i+=3;
258    }
259    else {
260      s[o++] = s[i++];
261    }
262  }
263  if (i && o) s[o] = '\0';
264  return (char *)s;
265}
266
267NEOERR *cgi_js_escape (const char *in, char **esc)
268{
269  return nerr_pass(neos_js_escape(in, esc));
270}
271
272NEOERR *cgi_url_escape (const char *buf, char **esc)
273{
274  return nerr_pass(neos_url_escape(buf, esc, NULL));
275}
276
277NEOERR *cgi_url_escape_more (const char *in, char **esc,
278                                 const char *other)
279{
280  return nerr_pass(neos_url_escape(in, esc, other));
281}
282
283NEOERR *cgi_url_validate (const char *buf, char **esc)
284{
285  return nerr_pass(neos_url_validate(buf, esc));
286}
287
288static NEOERR *_parse_query (CGI *cgi, char *query)
289{
290  NEOERR *err = STATUS_OK;
291  char *t, *k, *v, *l;
292  char buf[256];
293  char unnamed[10];
294  int unnamed_count = 0;
295  HDF *obj, *child;
296
297  if (query && *query)
298  {
299    k = strtok_r(query, "&", &l);
300    while (k && *k)
301    {
302      v = strchr(k, '=');
303      if (v == NULL)
304      {
305	v = "";
306      }
307      else
308      {
309	*v = '\0';
310	v++;
311      }
312
313
314      /* Check for some invalid query strings */
315      if (*k == 0) {
316        /*  '?=foo' gets mapped in as Query._1=foo */
317        snprintf(unnamed,sizeof(unnamed), "_%d", unnamed_count++);
318        k = unnamed;
319      } else if (*k == '.') {
320        /* an hdf element can't start with a period */
321        *k = '_';
322      }
323      snprintf(buf, sizeof(buf), "Query.%s", cgi_url_unescape(k));
324
325      if (!(cgi->ignore_empty_form_vars && (*v == '\0')))
326      {
327
328
329	cgi_url_unescape(v);
330	obj = hdf_get_obj (cgi->hdf, buf);
331	if (obj != NULL)
332	{
333	  int i = 0;
334	  char buf2[10];
335	  child = hdf_obj_child (obj);
336	  if (child == NULL)
337	  {
338	    t = hdf_obj_value (obj);
339	    err = hdf_set_value (obj, "0", t);
340	    if (err != STATUS_OK) break;
341	    i = 1;
342	  }
343	  else
344	  {
345	    while (child != NULL)
346	    {
347	      i++;
348	      child = hdf_obj_next (child);
349	      if (err != STATUS_OK) break;
350	    }
351	    if (err != STATUS_OK) break;
352	  }
353	  snprintf (buf2, sizeof(buf2), "%d", i);
354	  err = hdf_set_value (obj, buf2, v);
355	  if (err != STATUS_OK) break;
356	}
357	err = hdf_set_value (cgi->hdf, buf, v);
358	if (nerr_match(err, NERR_ASSERT)) {
359	  STRING str;
360
361	  string_init(&str);
362	  nerr_error_string(err, &str);
363	  ne_warn("Unable to set Query value: %s = %s: %s", buf, v, str.buf);
364	  string_clear(&str);
365	  nerr_ignore(&err);
366	}
367	if (err != STATUS_OK) break;
368      }
369      k = strtok_r(NULL, "&", &l);
370    }
371  }
372  return nerr_pass(err);
373}
374
375/* Is it an error if its a short read? */
376static NEOERR *_parse_post_form (CGI *cgi)
377{
378  NEOERR *err = STATUS_OK;
379  char *l, *query;
380  int len, r, o;
381
382  l = hdf_get_value (cgi->hdf, "CGI.ContentLength", NULL);
383  if (l == NULL) return STATUS_OK;
384  len = atoi (l);
385  if (len <= 0) return STATUS_OK;
386
387  cgi->data_expected = len;
388
389  query = (char *) malloc (sizeof(char) * (len + 1));
390  if (query == NULL)
391    return nerr_raise (NERR_NOMEM,
392	"Unable to allocate memory to read POST input of length %d", len);
393
394
395  o = 0;
396  while (o < len)
397  {
398    cgiwrap_read (query + o, len - o, &r);
399    if (r <= 0) break;
400    o = o + r;
401  }
402  if (r < 0)
403  {
404    free(query);
405    return nerr_raise_errno (NERR_IO, "Short read on CGI POST input (%d < %d)",
406	o, len);
407  }
408  if (o != len)
409  {
410    free(query);
411    return nerr_raise (NERR_IO, "Short read on CGI POST input (%d < %d)",
412	o, len);
413  }
414  query[len] = '\0';
415  err = _parse_query (cgi, query);
416  free(query);
417  return nerr_pass(err);
418}
419
420static NEOERR *_parse_cookie (CGI *cgi)
421{
422  NEOERR *err;
423  char *cookie;
424  char *k, *v, *l;
425  HDF *obj;
426
427  err = hdf_get_copy (cgi->hdf, "HTTP.Cookie", &cookie, NULL);
428  if (err != STATUS_OK) return nerr_pass(err);
429  if (cookie == NULL) return STATUS_OK;
430
431  err = hdf_set_value (cgi->hdf, "Cookie", cookie);
432  if (err != STATUS_OK)
433  {
434    free(cookie);
435    return nerr_pass(err);
436  }
437  obj = hdf_get_obj (cgi->hdf, "Cookie");
438
439  k = l = cookie;
440  while (*l && *l != '=' && *l != ';') l++;
441  while (*k)
442  {
443    if (*l == '=')
444    {
445      if (*l) *l++ = '\0';
446      v = l;
447      while (*l && *l != ';') l++;
448      if (*l) *l++ = '\0';
449    }
450    else
451    {
452      v = "";
453      if (*l) *l++ = '\0';
454    }
455    k = neos_strip (k);
456    v = neos_strip (v);
457    if (k[0] && v[0])
458    {
459      err = hdf_set_value (obj, k, v);
460      if (nerr_match(err, NERR_ASSERT)) {
461	STRING str;
462
463	string_init(&str);
464	nerr_error_string(err, &str);
465	ne_warn("Unable to set Cookie value: %s = %s: %s", k, v, str.buf);
466	string_clear(&str);
467	nerr_ignore(&err);
468      }
469      if (err) break;
470    }
471    k = l;
472    while (*l && *l != '=' && *l != ';') l++;
473  }
474
475  free (cookie);
476
477  return nerr_pass(err);
478}
479
480#ifdef ENABLE_REMOTE_DEBUG
481
482static void _launch_debugger (CGI *cgi, char *display)
483{
484  pid_t myPid, pid;
485  char buffer[127];
486  char *debugger;
487  HDF *obj;
488  char *allowed;
489
490  /* Only allow remote debugging from allowed hosts */
491  for (obj = hdf_get_child (cgi->hdf, "Config.Displays");
492      obj; obj = hdf_obj_next (obj))
493  {
494    allowed = hdf_obj_value (obj);
495    if (allowed && !strcmp (display, allowed)) break;
496  }
497  if (obj == NULL) return;
498
499  myPid = getpid();
500
501  if ((pid = fork()) < 0)
502    return;
503
504  if ((debugger = hdf_get_value (cgi->hdf, "Config.Debugger", NULL)) == NULL)
505  {
506    debugger = "/usr/local/bin/sudo /usr/local/bin/ddd -display %s %s %d";
507  }
508
509  if (!pid)
510  {
511    sprintf(buffer, debugger, display, Argv0, myPid);
512    execl("/bin/sh", "sh", "-c", buffer, NULL);
513  }
514  else
515  {
516    sleep(60);
517  }
518}
519
520#endif
521
522static NEOERR *cgi_pre_parse (CGI *cgi)
523{
524  NEOERR *err;
525  int x = 0;
526  char buf[256];
527  char *query;
528
529  while (CGIVars[x].env_name)
530  {
531    snprintf (buf, sizeof(buf), "CGI.%s", CGIVars[x].hdf_name);
532    err = _add_cgi_env_var(cgi, CGIVars[x].env_name, buf);
533    if (err != STATUS_OK) return nerr_pass (err);
534    x++;
535  }
536  x = 0;
537  while (HTTPVars[x].env_name)
538  {
539    snprintf (buf, sizeof(buf), "HTTP.%s", HTTPVars[x].hdf_name);
540    err = _add_cgi_env_var(cgi, HTTPVars[x].env_name, buf);
541    if (err != STATUS_OK) return nerr_pass (err);
542    x++;
543  }
544  err = _parse_cookie(cgi);
545  if (err != STATUS_OK) return nerr_pass (err);
546
547  err = hdf_get_copy (cgi->hdf, "CGI.QueryString", &query, NULL);
548  if (err != STATUS_OK) return nerr_pass (err);
549  if (query != NULL)
550  {
551    err = _parse_query(cgi, query);
552    free(query);
553    if (err != STATUS_OK) return nerr_pass (err);
554  }
555
556  {
557    char *d = hdf_get_value(cgi->hdf, "Query.debug_pause", NULL);
558    char *d_p = hdf_get_value(cgi->hdf, "Config.DebugPassword", NULL);
559
560    if (hdf_get_int_value(cgi->hdf, "Config.DebugEnabled", 0) &&
561        d && d_p && !strcmp(d, d_p)) {
562      sleep(20);
563    }
564  }
565
566#ifdef ENABLE_REMOTE_DEBUG
567  {
568    char *display;
569
570    display = hdf_get_value (cgi->hdf, "Query.xdisplay", NULL);
571    if (display)
572    {
573      fprintf(stderr, "** Got display %s\n", display);
574      _launch_debugger(cgi, display);
575    }
576  }
577#endif
578
579  return STATUS_OK;
580}
581
582NEOERR *cgi_register_parse_cb(CGI *cgi, const char *method, const char *ctype,
583                              void *rock, CGI_PARSE_CB parse_cb)
584{
585  struct _cgi_parse_cb *my_pcb;
586
587  if (method == NULL || ctype == NULL)
588    return nerr_raise(NERR_ASSERT, "method and type must not be NULL to register cb");
589
590  my_pcb = (struct _cgi_parse_cb *) calloc(1, sizeof(struct _cgi_parse_cb));
591  if (my_pcb == NULL)
592    return nerr_raise(NERR_NOMEM, "Unable to allocate memory to register parse cb");
593
594  my_pcb->method = strdup(method);
595  my_pcb->ctype = strdup(ctype);
596  if (my_pcb->method == NULL || my_pcb->ctype == NULL)
597  {
598    if (my_pcb->method != NULL)
599      free(my_pcb->method);
600    if (my_pcb->ctype != NULL)
601      free(my_pcb->ctype);
602    free(my_pcb);
603    return nerr_raise(NERR_NOMEM, "Unable to allocate memory to register parse cb");
604  }
605  if (!strcmp(my_pcb->method, "*"))
606    my_pcb->any_method = 1;
607  if (!strcmp(my_pcb->ctype, "*"))
608    my_pcb->any_ctype = 1;
609  my_pcb->rock = rock;
610  my_pcb->parse_cb = parse_cb;
611  my_pcb->next = cgi->parse_callbacks;
612  cgi->parse_callbacks = my_pcb;
613  return STATUS_OK;
614}
615
616NEOERR *cgi_parse (CGI *cgi)
617{
618  NEOERR *err;
619  char *method, *type;
620  struct _cgi_parse_cb *pcb;
621
622
623  method = hdf_get_value (cgi->hdf, "CGI.RequestMethod", "GET");
624  type = hdf_get_value (cgi->hdf, "CGI.ContentType", NULL);
625  /* Walk the registered parse callbacks for a matching one */
626  pcb = cgi->parse_callbacks;
627  while (pcb != NULL)
628  {
629    if ( (pcb->any_method || !strcasecmp(pcb->method, method)) &&
630	 (pcb->any_ctype || (type && !strcasecmp(pcb->ctype, type))) )
631    {
632      err = pcb->parse_cb(cgi, method, type, pcb->rock);
633      if (err && !nerr_handle(&err, CGIParseNotHandled))
634	return nerr_pass(err);
635    }
636    pcb = pcb->next;
637  }
638
639  /* Fallback to internal methods */
640
641  if (!strcmp(method, "POST"))
642  {
643    if (type && !strcmp(type, "application/x-www-form-urlencoded"))
644    {
645      err = _parse_post_form(cgi);
646      if (err != STATUS_OK) return nerr_pass (err);
647    }
648    else if (type && !strncmp (type, "multipart/form-data", 19))
649    {
650      err = parse_rfc2388 (cgi);
651      if (err != STATUS_OK) return nerr_pass (err);
652    }
653#if 0
654    else
655    {
656      int len, x, r;
657      char *l;
658      char buf[4096];
659      FILE *fp;
660
661      fp = fopen("/tmp/upload", "w");
662
663      l = hdf_get_value (cgi->hdf, "CGI.ContentLength", NULL);
664      if (l == NULL) return STATUS_OK;
665      len = atoi (l);
666
667      x = 0;
668      while (x < len)
669      {
670	if (len-x > sizeof(buf))
671	  cgiwrap_read (buf, sizeof(buf), &r);
672	else
673	  cgiwrap_read (buf, len - x, &r);
674	fwrite (buf, 1, r, fp);
675	x += r;
676      }
677      fclose (fp);
678      if (err) return nerr_pass(err);
679    }
680#endif
681  }
682  else if (!strcmp(method, "PUT"))
683  {
684    FILE *fp;
685    int len, x, r, w;
686    char *l;
687    char buf[4096];
688    int unlink_files = hdf_get_int_value(cgi->hdf, "Config.Upload.Unlink", 1);
689
690    err = open_upload(cgi, unlink_files, &fp);
691    if (err) return nerr_pass(err);
692
693    l = hdf_get_value (cgi->hdf, "CGI.ContentLength", NULL);
694    if (l == NULL) return STATUS_OK;
695    len = atoi (l);
696    if (len <= 0) return STATUS_OK;
697
698    x = 0;
699    while (x < len)
700    {
701      if (len-x > sizeof(buf))
702	cgiwrap_read (buf, sizeof(buf), &r);
703      else
704	cgiwrap_read (buf, len - x, &r);
705      w = fwrite (buf, sizeof(char), r, fp);
706      if (w != r)
707      {
708	err = nerr_raise_errno(NERR_IO, "Short write on PUT: %d < %d", w, r);
709	break;
710      }
711      x += r;
712    }
713    if (err) return nerr_pass(err);
714    fseek(fp, 0, SEEK_SET);
715    l = hdf_get_value(cgi->hdf, "CGI.PathInfo", NULL);
716    if (l) err = hdf_set_value (cgi->hdf, "PUT", l);
717    if (err) return nerr_pass(err);
718    if (type) err = hdf_set_value (cgi->hdf, "PUT.Type", type);
719    if (err) return nerr_pass(err);
720    err = hdf_set_int_value (cgi->hdf, "PUT.FileHandle", uListLength(cgi->files));
721    if (err) return nerr_pass(err);
722    if (!unlink_files)
723    {
724      char *name;
725      err = uListGet(cgi->filenames, uListLength(cgi->filenames)-1,
726	  (void *)&name);
727      if (err) return nerr_pass(err);
728      err = hdf_set_value (cgi->hdf, "PUT.FileName", name);
729      if (err) return nerr_pass(err);
730    }
731  }
732  return STATUS_OK;
733}
734
735NEOERR *cgi_init (CGI **cgi, HDF *hdf)
736{
737  NEOERR *err = STATUS_OK;
738  CGI *mycgi;
739
740  if (ExceptionsInit == 0)
741  {
742    err = nerr_init();
743    if (err) return nerr_pass(err);
744    err = nerr_register(&CGIFinished, "CGIFinished");
745    if (err) return nerr_pass(err);
746    err = nerr_register(&CGIUploadCancelled, "CGIUploadCancelled");
747    if (err) return nerr_pass(err);
748    err = nerr_register(&CGIUploadCancelled, "CGIParseNotHandled");
749    if (err) return nerr_pass(err);
750    ExceptionsInit = 1;
751  }
752
753  *cgi = NULL;
754  mycgi = (CGI *) calloc (1, sizeof(CGI));
755  if (mycgi == NULL)
756    return nerr_raise(NERR_NOMEM, "Unable to allocate space for CGI");
757
758  mycgi->time_start = ne_timef();
759
760  mycgi->ignore_empty_form_vars = IgnoreEmptyFormVars;
761
762  do
763  {
764    if (hdf == NULL)
765    {
766      err = hdf_init (&(mycgi->hdf));
767      if (err != STATUS_OK) break;
768    }
769    else
770    {
771      mycgi->hdf = hdf;
772    }
773    err = cgi_pre_parse (mycgi);
774    if (err != STATUS_OK) break;
775
776  } while (0);
777
778  if (err == STATUS_OK)
779    *cgi = mycgi;
780  else
781  {
782    cgi_destroy(&mycgi);
783  }
784  return nerr_pass(err);
785}
786
787static void _destroy_tmp_file(char *filename)
788{
789  unlink(filename);
790  free(filename);
791}
792
793void cgi_destroy (CGI **cgi)
794{
795  CGI *my_cgi;
796
797  if (!cgi || !*cgi)
798    return;
799  my_cgi = *cgi;
800  if (my_cgi->hdf)
801    hdf_destroy (&(my_cgi->hdf));
802  if (my_cgi->buf)
803    free(my_cgi->buf);
804  if (my_cgi->files)
805    uListDestroyFunc(&(my_cgi->files), (void (*)(void *))fclose);
806  if (my_cgi->filenames)
807    uListDestroyFunc(&(my_cgi->filenames), (void (*)(void *))_destroy_tmp_file);
808  free (*cgi);
809  *cgi = NULL;
810}
811
812static NEOERR *render_cb (void *ctx, char *buf)
813{
814  STRING *str = (STRING *)ctx;
815  NEOERR *err;
816
817  err = nerr_pass(string_append(str, buf));
818  return err;
819}
820
821static NEOERR *cgi_headers (CGI *cgi)
822{
823  NEOERR *err = STATUS_OK;
824  HDF *obj, *child;
825  char *s, *charset = NULL;
826
827  if (hdf_get_int_value (cgi->hdf, "Config.NoCache", 0))
828  {
829    /* Ok, we try really hard to defeat caches here */
830    /* this isn't in any HTTP rfc's, it just seems to be a convention */
831    err = cgiwrap_writef ("Pragma: no-cache\r\n");
832    if (err != STATUS_OK) return nerr_pass (err);
833    err = cgiwrap_writef ("Expires: Fri, 01 Jan 1990 00:00:00 GMT\r\n");
834    if (err != STATUS_OK) return nerr_pass (err);
835    err = cgiwrap_writef ("Cache-control: no-cache, must-revalidate, no-cache=\"Set-Cookie\", private\r\n");
836    if (err != STATUS_OK) return nerr_pass (err);
837  }
838  obj = hdf_get_obj (cgi->hdf, "cgiout");
839  if (obj)
840  {
841    s = hdf_get_value (obj, "Status", NULL);
842    if (s)
843      err = cgiwrap_writef ("Status: %s\r\n", s);
844    if (err != STATUS_OK) return nerr_pass (err);
845    s = hdf_get_value (obj, "Location", NULL);
846    if (s)
847      err = cgiwrap_writef ("Location: %s\r\n", s);
848    if (err != STATUS_OK) return nerr_pass (err);
849    child = hdf_get_obj (cgi->hdf, "cgiout.other");
850    if (child)
851    {
852      child = hdf_obj_child (child);
853      while (child != NULL)
854      {
855	s = hdf_obj_value (child);
856	err = cgiwrap_writef ("%s\r\n", s);
857	if (err != STATUS_OK) return nerr_pass (err);
858	child = hdf_obj_next(child);
859      }
860    }
861    charset = hdf_get_value (obj, "charset", NULL);
862    s = hdf_get_value (obj, "ContentType", "text/html");
863    if (charset)
864      err = cgiwrap_writef ("Content-Type: %s; charset=%s\r\n\r\n", s, charset);
865    else
866      err = cgiwrap_writef ("Content-Type: %s\r\n\r\n", s);
867    if (err != STATUS_OK) return nerr_pass (err);
868  }
869  else
870  {
871    /* Default */
872    err = cgiwrap_writef ("Content-Type: text/html\r\n\r\n");
873    if (err != STATUS_OK) return nerr_pass (err);
874  }
875  return STATUS_OK;
876}
877
878#if defined(HTML_COMPRESSION)
879/* Copy these here from zutil.h, which we aren't supposed to include */
880#define DEF_MEM_LEVEL 8
881#define OS_CODE 0x03
882
883static NEOERR *cgi_compress (STRING *str, char *obuf, int *olen)
884{
885  z_stream stream;
886  int err;
887
888  stream.next_in = (Bytef*)str->buf;
889  stream.avail_in = (uInt)str->len;
890  stream.next_out = (Bytef*)obuf;
891  stream.avail_out = (uInt)*olen;
892  if ((uLong)stream.avail_out != *olen)
893    return nerr_raise(NERR_NOMEM, "Destination too big: %d", *olen);
894
895  stream.zalloc = (alloc_func)0;
896  stream.zfree = (free_func)0;
897  stream.opaque = (voidpf)0;
898
899  /* err = deflateInit(&stream, Z_DEFAULT_COMPRESSION); */
900  err = deflateInit2(&stream, Z_DEFAULT_COMPRESSION, Z_DEFLATED, -MAX_WBITS, DEF_MEM_LEVEL, Z_DEFAULT_STRATEGY);
901  if (err != Z_OK)
902    return nerr_raise(NERR_SYSTEM, "deflateInit2 returned %d", err);
903
904  err = deflate(&stream, Z_FINISH);
905  if (err != Z_STREAM_END) {
906    deflateEnd(&stream);
907    return nerr_raise(NERR_SYSTEM, "deflate returned %d", err);
908  }
909  *olen = stream.total_out;
910
911  err = deflateEnd(&stream);
912  return STATUS_OK;
913}
914#endif
915
916/* This ws strip function is Dave's version, designed to make debug
917 * easier, and the output a bit smaller... but not as small as it could
918 * be: essentially, it strips all empty lines, all extra space at the
919 * end of the line, except in pre/textarea tags.
920 *
921 * Ok, expanding to 3 levels:
922 * 0 - No stripping
923 * 1 - Dave's debug stripper (as above)
924 * 2 - strip all extra white space
925 *
926 * We don't currently strip white space in a tag
927 *
928 * */
929
930#if 0
931static void debug_output(char *header, char *s, int n)
932{
933  int x;
934  fprintf (stderr, "%s ", header);
935  for (x = 0; x < n; x++)
936  {
937    fprintf (stderr, "%c", s[x]);
938  }
939  fprintf(stderr, "\n");
940}
941#endif
942
943void cgi_html_ws_strip(STRING *str, int level)
944{
945  int ws = 0;
946  int seen_nonws = level > 1;
947  int i, o, l;
948  char *ch;
949
950  i = o = 0;
951  if (str->len) {
952    ws = isspace(str->buf[0]);
953  }
954  while (i < str->len)
955  {
956    if (str->buf[i] == '<')
957    {
958      str->buf[o++] = str->buf[i++];
959      if (!strncasecmp(str->buf + i, "textarea", 8))
960      {
961	ch = str->buf + i;
962	do
963	{
964	  ch = strchr(ch, '<');
965	  if (ch == NULL)
966	  {
967	    memmove(str->buf + o, str->buf + i, str->len - i);
968	    str->len = o + str->len - i;
969	    str->buf[str->len] = '\0';
970	    return;
971	  }
972	  ch++;
973	} while (strncasecmp(ch, "/textarea>", 10));
974	ch += 10;
975	l = ch - str->buf - i;
976	memmove(str->buf + o, str->buf + i, l);
977	o += l;
978	i += l;
979      }
980      else if (!strncasecmp(str->buf + i, "pre", 3))
981      {
982	ch = str->buf + i;
983	do
984	{
985	  ch = strchr(ch, '<');
986	  if (ch == NULL)
987	  {
988	    memmove(str->buf + o, str->buf + i, str->len - i);
989	    str->len = o + str->len - i;
990	    str->buf[str->len] = '\0';
991	    return;
992	  }
993	  ch++;
994	} while (strncasecmp(ch, "/pre>", 5));
995	ch += 5;
996	l = ch - str->buf - i;
997	memmove(str->buf + o, str->buf + i, l);
998	o += l;
999	i += l;
1000      }
1001      else
1002      {
1003	ch = strchr(str->buf + i, '>');
1004	if (ch == NULL)
1005	{
1006	  memmove(str->buf + o, str->buf + i, str->len - i);
1007	  str->len = o + str->len - i;
1008	  str->buf[str->len] = '\0';
1009	  return;
1010	}
1011	ch++;
1012	/* debug_output("copying tag", str->buf + i, ch - str->buf - i);
1013	 * */
1014	l = ch - str->buf - i;
1015	memmove(str->buf + o, str->buf + i, l);
1016	o += l;
1017	i += l;
1018      }
1019      /* debug_output("result", str->buf, o); */
1020      seen_nonws = 1;
1021      ws = 0;
1022    }
1023    else if (str->buf[i] == '\n')
1024    {
1025      /* This has the effect of erasing all whitespace at the eol plus
1026       * erasing all blank lines */
1027      while (o && isspace(str->buf[o-1])) o--;
1028      str->buf[o++] = str->buf[i++];
1029      ws = level > 1;
1030      seen_nonws = level > 1;
1031    }
1032    else if (seen_nonws && isspace(str->buf[i]))
1033    {
1034      if (ws)
1035      {
1036	i++;
1037      }
1038      else
1039      {
1040	str->buf[o++] = str->buf[i++];
1041	ws = 1;
1042      }
1043    }
1044    else
1045    {
1046      seen_nonws = 1;
1047      ws = 0;
1048      str->buf[o++] = str->buf[i++];
1049    }
1050  }
1051
1052  str->len = o;
1053  str->buf[str->len] = '\0';
1054}
1055
1056NEOERR *cgi_output (CGI *cgi, STRING *str)
1057{
1058  NEOERR *err = STATUS_OK;
1059  double dis;
1060  int is_html = 0;
1061  int use_deflate = 0;
1062  int use_gzip = 0;
1063  int do_debug = 0;
1064  int do_timefooter = 0;
1065  int ws_strip_level = 0;
1066  char *s, *e;
1067
1068  s = hdf_get_value (cgi->hdf, "Query.debug", NULL);
1069  e = hdf_get_value (cgi->hdf, "Config.DebugPassword", NULL);
1070  if (hdf_get_int_value(cgi->hdf, "Config.DebugEnabled", 0) &&
1071      s && e && !strcmp(s, e)) do_debug = 1;
1072  do_timefooter = hdf_get_int_value (cgi->hdf, "Config.TimeFooter", 1);
1073  ws_strip_level = hdf_get_int_value (cgi->hdf, "Config.WhiteSpaceStrip", 1);
1074
1075  dis = ne_timef();
1076  s = hdf_get_value (cgi->hdf, "cgiout.ContentType", "text/html");
1077  if (!strcasecmp(s, "text/html"))
1078    is_html = 1;
1079
1080#if defined(HTML_COMPRESSION)
1081  /* Determine whether or not we can compress the output */
1082  if (is_html && hdf_get_int_value (cgi->hdf, "Config.CompressionEnabled", 0))
1083  {
1084    err = hdf_get_copy (cgi->hdf, "HTTP.AcceptEncoding", &s, NULL);
1085    if (err != STATUS_OK) return nerr_pass (err);
1086    if (s)
1087    {
1088      char *next;
1089
1090      e = strtok_r (s, ",", &next);
1091      while (e && !use_deflate)
1092      {
1093	if (strstr(e, "deflate") != NULL)
1094	{
1095	  use_deflate = 1;
1096	  use_gzip = 0;
1097	}
1098	else if (strstr(e, "gzip") != NULL)
1099	  use_gzip = 1;
1100	e = strtok_r (NULL, ",", &next);
1101      }
1102      free (s);
1103    }
1104    s = hdf_get_value (cgi->hdf, "HTTP.UserAgent", NULL);
1105    if (s)
1106    {
1107      if (strstr(s, "MSIE 4") || strstr(s, "MSIE 5") || strstr(s, "MSIE 6"))
1108      {
1109	e = hdf_get_value (cgi->hdf, "HTTP.Accept", NULL);
1110	if (e && !strcmp(e, "*/*"))
1111	{
1112	  use_deflate = 0;
1113	  use_gzip = 0;
1114	}
1115      }
1116      else
1117      {
1118	if (strncasecmp(s, "mozilla/5.", 10))
1119	{
1120	  use_deflate = 0;
1121	  use_gzip = 0;
1122	}
1123      }
1124    }
1125    else
1126    {
1127      use_deflate = 0;
1128      use_gzip = 0;
1129    }
1130    if (use_deflate)
1131    {
1132      err = hdf_set_value (cgi->hdf, "cgiout.other.encoding",
1133	  "Content-Encoding: deflate");
1134    }
1135    else if (use_gzip)
1136    {
1137      err = hdf_set_value (cgi->hdf, "cgiout.other.encoding",
1138	  "Content-Encoding: gzip");
1139    }
1140    if (err != STATUS_OK) return nerr_pass(err);
1141  }
1142#endif
1143
1144  err = cgi_headers(cgi);
1145  if (err != STATUS_OK) return nerr_pass(err);
1146
1147  if (is_html)
1148  {
1149    char buf[50];
1150    int x;
1151
1152    if (do_timefooter)
1153    {
1154      snprintf (buf, sizeof(buf), "\n<!-- %5.3f:%d -->\n",
1155	  dis - cgi->time_start, use_deflate || use_gzip);
1156      err = string_append (str, buf);
1157      if (err != STATUS_OK) return nerr_pass(err);
1158    }
1159
1160    if (ws_strip_level)
1161    {
1162      cgi_html_ws_strip(str, ws_strip_level);
1163    }
1164
1165    if (do_debug)
1166    {
1167      err = string_append (str, "<hr>");
1168      if (err != STATUS_OK) return nerr_pass(err);
1169      x = 0;
1170      while (1)
1171      {
1172	char *k, *v;
1173	err = cgiwrap_iterenv (x, &k, &v);
1174	if (err != STATUS_OK) return nerr_pass(err);
1175	if (k == NULL) break;
1176	err =string_appendf (str, "%s = %s<br>", k, v);
1177	if (err != STATUS_OK) return nerr_pass(err);
1178	free(k);
1179	free(v);
1180	x++;
1181      }
1182      err = string_append (str, "<pre>");
1183      if (err != STATUS_OK) return nerr_pass(err);
1184      err = hdf_dump_str (cgi->hdf, NULL, 0, str);
1185      if (err != STATUS_OK) return nerr_pass(err);
1186    }
1187  }
1188
1189#if defined(HTML_COMPRESSION)
1190    if (is_html && (use_deflate || use_gzip))
1191    {
1192      char *dest;
1193      static int gz_magic[2] = {0x1f, 0x8b}; /* gzip magic header */
1194      char gz_buf[20]; /* gzip header/footer buffer, len of header is 10 bytes */
1195      unsigned int crc = 0;
1196      int len2;
1197
1198      if (use_gzip)
1199      {
1200	crc = crc32(0L, Z_NULL, 0);
1201	crc = crc32(crc, (const Bytef *)(str->buf), str->len);
1202      }
1203      len2 = str->len * 2;
1204      dest = (char *) malloc (sizeof(char) * len2);
1205      if (dest != NULL)
1206      {
1207	do {
1208	  err = cgi_compress (str, dest, &len2);
1209	  if (err == STATUS_OK)
1210	  {
1211	    if (use_gzip)
1212	    {
1213	      /* I'm using sprintf instead of cgiwrap_writef since
1214	       * the wrapper writef might not handle values with
1215	       * embedded NULLs... though I should fix the python one
1216	       * now as well */
1217	      sprintf(gz_buf, "%c%c%c%c%c%c%c%c%c%c", gz_magic[0], gz_magic[1],
1218		  Z_DEFLATED, 0 /*flags*/, 0,0,0,0 /*time*/, 0 /*xflags*/,
1219		  OS_CODE);
1220	      err = cgiwrap_write(gz_buf, 10);
1221	    }
1222	    if (err != STATUS_OK) break;
1223	    err = cgiwrap_write(dest, len2);
1224	    if (err != STATUS_OK) break;
1225
1226	    if (use_gzip)
1227	    {
1228	      /* write crc and len in network order */
1229	      sprintf(gz_buf, "%c%c%c%c%c%c%c%c",
1230		  (0xff & (crc >> 0)),
1231		  (0xff & (crc >> 8)),
1232		  (0xff & (crc >> 16)),
1233		  (0xff & (crc >> 24)),
1234		  (0xff & (str->len >> 0)),
1235		  (0xff & (str->len >> 8)),
1236		  (0xff & (str->len >> 16)),
1237		  (0xff & (str->len >> 24)));
1238	      err = cgiwrap_write(gz_buf, 8);
1239	      if (err != STATUS_OK) break;
1240	    }
1241	  }
1242	  else
1243	  {
1244	    nerr_log_error (err);
1245	    err = cgiwrap_write(str->buf, str->len);
1246	  }
1247	} while (0);
1248	free (dest);
1249      }
1250      else
1251      {
1252	err = cgiwrap_write(str->buf, str->len);
1253      }
1254    }
1255    else
1256#endif
1257    {
1258      err = cgiwrap_write(str->buf, str->len);
1259    }
1260
1261  return nerr_pass(err);
1262}
1263
1264NEOERR *cgi_html_escape_strfunc(const char *str, char **ret)
1265{
1266  return nerr_pass(html_escape_alloc(str, strlen(str), ret));
1267}
1268
1269NEOERR *cgi_html_strip_strfunc(const char *str, char **ret)
1270{
1271  return nerr_pass(html_strip_alloc(str, strlen(str), ret));
1272}
1273
1274NEOERR *cgi_text_html_strfunc(const char *str, char **ret)
1275{
1276  return nerr_pass(convert_text_html_alloc(str, strlen(str), ret));
1277}
1278
1279NEOERR *cgi_register_strfuncs(CSPARSE *cs)
1280{
1281  NEOERR *err;
1282
1283  err = cs_register_esc_strfunc(cs, "url_escape", cgi_url_escape);
1284  if (err != STATUS_OK) return nerr_pass(err);
1285  err = cs_register_esc_strfunc(cs, "html_escape", cgi_html_escape_strfunc);
1286  if (err != STATUS_OK) return nerr_pass(err);
1287  err = cs_register_strfunc(cs, "text_html", cgi_text_html_strfunc);
1288  if (err != STATUS_OK) return nerr_pass(err);
1289  err = cs_register_esc_strfunc(cs, "js_escape", cgi_js_escape);
1290  if (err != STATUS_OK) return nerr_pass(err);
1291  err = cs_register_strfunc(cs, "html_strip", cgi_html_strip_strfunc);
1292  if (err != STATUS_OK) return nerr_pass(err);
1293  err = cs_register_esc_strfunc(cs, "url_validate", cgi_url_validate);
1294  if (err != STATUS_OK) return nerr_pass(err);
1295  return STATUS_OK;
1296}
1297
1298NEOERR *cgi_cs_init(CGI *cgi, CSPARSE **cs)
1299{
1300  NEOERR *err;
1301
1302  *cs = NULL;
1303
1304  do
1305  {
1306    err = cs_init (cs, cgi->hdf);
1307    if (err != STATUS_OK) break;
1308    err = cgi_register_strfuncs(*cs);
1309    if (err != STATUS_OK) break;
1310  } while (0);
1311
1312  if (err && *cs) cs_destroy(cs);
1313  return nerr_pass(err);
1314}
1315
1316NEOERR *cgi_display (CGI *cgi, const char *cs_file)
1317{
1318  NEOERR *err = STATUS_OK;
1319  char *debug;
1320  CSPARSE *cs = NULL;
1321  STRING str;
1322  int do_dump = 0;
1323  char *t;
1324
1325  string_init(&str);
1326
1327  debug = hdf_get_value (cgi->hdf, "Query.debug", NULL);
1328  t = hdf_get_value (cgi->hdf, "Config.DumpPassword", NULL);
1329  if (hdf_get_int_value(cgi->hdf, "Config.DebugEnabled", 0) &&
1330      debug && t && !strcmp (debug, t)) do_dump = 1;
1331
1332  do
1333  {
1334    err = cs_init (&cs, cgi->hdf);
1335    if (err != STATUS_OK) break;
1336    err = cgi_register_strfuncs(cs);
1337    if (err != STATUS_OK) break;
1338    err = cs_parse_file (cs, cs_file);
1339    if (err != STATUS_OK) break;
1340    if (do_dump)
1341    {
1342      cgiwrap_writef("Content-Type: text/plain\n\n");
1343      hdf_dump_str(cgi->hdf, "", 0, &str);
1344      cs_dump(cs, &str, render_cb);
1345      cgiwrap_writef("%s", str.buf);
1346      break;
1347    }
1348    else
1349    {
1350      err = cs_render (cs, &str, render_cb);
1351      if (err != STATUS_OK) break;
1352    }
1353    err = cgi_output(cgi, &str);
1354    if (err != STATUS_OK) break;
1355  } while (0);
1356
1357  cs_destroy(&cs);
1358  string_clear (&str);
1359  return nerr_pass(err);
1360}
1361
1362void cgi_neo_error (CGI *cgi, NEOERR *err)
1363{
1364  STRING str;
1365
1366  string_init(&str);
1367  cgiwrap_writef("Status: 500\n");
1368  cgiwrap_writef("Content-Type: text/html\n\n");
1369
1370  cgiwrap_writef("<html><body>\nAn error occured:<pre>");
1371  nerr_error_traceback(err, &str);
1372  cgiwrap_write(str.buf, str.len);
1373  cgiwrap_writef("</pre></body></html>\n");
1374}
1375
1376void cgi_error (CGI *cgi, const char *fmt, ...)
1377{
1378  va_list ap;
1379
1380  cgiwrap_writef("Status: 500\n");
1381  cgiwrap_writef("Content-Type: text/html\n\n");
1382  cgiwrap_writef("<html><body>\nAn error occured:<pre>");
1383  va_start (ap, fmt);
1384  cgiwrap_writevf (fmt, ap);
1385  va_end (ap);
1386  cgiwrap_writef("</pre></body></html>\n");
1387}
1388
1389void cgi_debug_init (int argc, char **argv)
1390{
1391  FILE *fp;
1392  char line[4096];
1393  char *v, *k;
1394
1395  Argv0 = argv[0];
1396
1397  if (argc)
1398  {
1399    fp = fopen(argv[1], "r");
1400    if (fp == NULL)
1401      return;
1402
1403    while (fgets(line, sizeof(line), fp) != NULL)
1404    {
1405      v = strchr(line, '=');
1406      if (v != NULL)
1407      {
1408	*v = '\0';
1409	v = neos_strip(v+1);
1410	k = neos_strip(line);
1411	cgiwrap_putenv (line, v);
1412      }
1413    }
1414    fclose(fp);
1415  }
1416}
1417
1418void cgi_vredirect (CGI *cgi, int uri, const char *fmt, va_list ap)
1419{
1420  cgiwrap_writef ("Status: 302\r\n");
1421  cgiwrap_writef ("Content-Type: text/html\r\n");
1422  cgiwrap_writef ("Pragma: no-cache\r\n");
1423  cgiwrap_writef ("Expires: Fri, 01 Jan 1999 00:00:00 GMT\r\n");
1424  cgiwrap_writef ("Cache-control: no-cache, no-cache=\"Set-Cookie\", private\r\n");
1425
1426  if (uri)
1427  {
1428    cgiwrap_writef ("Location: ");
1429  }
1430  else
1431  {
1432    char *host;
1433    int https = 0;
1434
1435    if (!strcmp(hdf_get_value(cgi->hdf, "CGI.HTTPS", "off"), "on"))
1436    {
1437      https = 1;
1438    }
1439
1440    host = hdf_get_value (cgi->hdf, "HTTP.Host", NULL);
1441    if (host == NULL)
1442      host = hdf_get_value (cgi->hdf, "CGI.ServerName", "localhost");
1443
1444    cgiwrap_writef ("Location: %s://%s", https ? "https" : "http", host);
1445
1446    if ((strchr(host, ':') == NULL)) {
1447      int port = hdf_get_int_value(cgi->hdf, "CGI.ServerPort", 80);
1448
1449      if (!((https && port == 443) || (!https && port == 80)))
1450      {
1451	cgiwrap_writef(":%d", port);
1452      }
1453    }
1454  }
1455  cgiwrap_writevf (fmt, ap);
1456  cgiwrap_writef ("\r\n\r\n");
1457  cgiwrap_writef ("Redirect page<br><br>\n");
1458#if 0
1459  /* Apparently this crashes on some computers... I don't know if its
1460   * legal to reuse the va_list */
1461  cgiwrap_writef ("  Destination: <A HREF=\"");
1462  cgiwrap_writevf (fmt, ap);
1463  cgiwrap_writef ("\">");
1464  cgiwrap_writevf (fmt, ap);
1465  cgiwrap_writef ("</A><BR>\n<BR>\n");
1466#endif
1467  cgiwrap_writef ("There is nothing to see here, please move along...");
1468
1469}
1470
1471void cgi_redirect (CGI *cgi, const char *fmt, ...)
1472{
1473  va_list ap;
1474
1475  va_start(ap, fmt);
1476  cgi_vredirect (cgi, 0, fmt, ap);
1477  va_end(ap);
1478  return;
1479}
1480
1481void cgi_redirect_uri (CGI *cgi, const char *fmt, ...)
1482{
1483  va_list ap;
1484
1485  va_start(ap, fmt);
1486  cgi_vredirect (cgi, 1, fmt, ap);
1487  va_end(ap);
1488  return;
1489}
1490
1491char *cgi_cookie_authority (CGI *cgi, const char *host)
1492{
1493  HDF *obj;
1494  char *domain;
1495  int hlen = 0, dlen = 0;
1496
1497  if (host == NULL)
1498  {
1499    host = hdf_get_value (cgi->hdf, "HTTP.Host", NULL);
1500  }
1501  if (host == NULL) return NULL;
1502
1503  while (host[hlen] && host[hlen] != ':') hlen++;
1504
1505  obj = hdf_get_obj (cgi->hdf, "CookieAuthority");
1506  if (obj == NULL) return NULL;
1507  for (obj = hdf_obj_child (obj);
1508       obj;
1509       obj = hdf_obj_next (obj))
1510  {
1511    domain = hdf_obj_value (obj);
1512    dlen = strlen(domain);
1513    if (hlen >= dlen)
1514    {
1515      if (!strncasecmp (host + hlen - dlen, domain, dlen))
1516	return domain;
1517    }
1518  }
1519
1520  return NULL;
1521}
1522
1523/* For more information about Cookies, see:
1524 * The original Netscape Cookie Spec:
1525 * http://wp.netscape.com/newsref/std/cookie_spec.html
1526 *
1527 * HTTP State Management Mechanism
1528 * http://www.ietf.org/rfc/rfc2109.txt
1529 */
1530
1531NEOERR *cgi_cookie_set (CGI *cgi, const char *name, const char *value,
1532                        const char *path, const char *domain,
1533                        const char *time_str, int persistent, int secure)
1534{
1535  NEOERR *err;
1536  STRING str;
1537  char my_time[256];
1538
1539  if (path == NULL) path = "/";
1540
1541  string_init(&str);
1542  do {
1543    err = string_appendf(&str, "Set-Cookie: %s=%s; path=%s", name, value, path);
1544    if (err) break;
1545
1546    if (persistent)
1547    {
1548      if (time_str == NULL)
1549      {
1550	/* Expires in one year */
1551	time_t exp_date = time(NULL) + 31536000;
1552
1553	strftime (my_time, 48, "%A, %d-%b-%Y 23:59:59 GMT",
1554	    gmtime (&exp_date));
1555	time_str = my_time;
1556      }
1557      err = string_appendf(&str, "; expires=%s", time_str);
1558      if (err) break;
1559    }
1560    if (domain)
1561    {
1562      err = string_appendf(&str, "; domain=%s", domain);
1563      if (err) break;
1564    }
1565    if (secure)
1566    {
1567      err = string_append(&str, "; secure");
1568      if (err) break;
1569    }
1570    err = string_append(&str, "\r\n");
1571  } while (0);
1572  if (err)
1573  {
1574    string_clear(&str);
1575    return nerr_pass(err);
1576  }
1577  cgiwrap_write(str.buf, str.len);
1578  string_clear(&str);
1579  return STATUS_OK;
1580}
1581
1582/* This will actually issue up to three set cookies, attempting to clear
1583 * the damn thing. */
1584NEOERR *cgi_cookie_clear (CGI *cgi, const char *name, const char *domain,
1585                          const char *path)
1586{
1587  if (path == NULL) path = "/";
1588  if (domain)
1589  {
1590    if (domain[0] == '.')
1591    {
1592      cgiwrap_writef ("Set-Cookie: %s=; path=%s; domain=%s;"
1593	  "expires=Thursday, 01-Jan-1970 00:00:00 GMT\r\n", name, path,
1594	  domain + 1);
1595    }
1596    cgiwrap_writef("Set-Cookie: %s=; path=%s; domain=%s;"
1597	"expires=Thursday, 01-Jan-1970 00:00:00 GMT\r\n", name, path,
1598	domain);
1599  }
1600  cgiwrap_writef("Set-Cookie: %s=; path=%s; "
1601      "expires=Thursday, 01-Jan-1970 00:00:00 GMT\r\n", name, path);
1602
1603  return STATUS_OK;
1604}
1605