/* * Copyright 2001-2004 Brandon Long * All Rights Reserved. * * ClearSilver Templating System * * This code is made available under the terms of the ClearSilver License. * http://www.clearsilver.net/license.hdf * */ #include "cs_config.h" #include <unistd.h> #include <string.h> #include <stdlib.h> #include <ctype.h> #include <stdio.h> #include <stdarg.h> #include <time.h> #if defined(HTML_COMPRESSION) #include <zlib.h> #endif #include "util/neo_misc.h" #include "util/neo_err.h" #include "util/neo_hdf.h" #include "util/neo_str.h" #include "cgi.h" #include "cgiwrap.h" #include "html.h" #include "cs/cs.h" struct _cgi_vars { char *env_name; char *hdf_name; } CGIVars[] = { {"AUTH_TYPE", "AuthType"}, {"CONTENT_TYPE", "ContentType"}, {"CONTENT_LENGTH", "ContentLength"}, {"DOCUMENT_ROOT", "DocumentRoot"}, {"GATEWAY_INTERFACE", "GatewayInterface"}, {"PATH_INFO", "PathInfo"}, {"PATH_TRANSLATED", "PathTranslated"}, {"QUERY_STRING", "QueryString"}, {"REDIRECT_REQUEST", "RedirectRequest"}, {"REDIRECT_QUERY_STRING", "RedirectQueryString"}, {"REDIRECT_STATUS", "RedirectStatus"}, {"REDIRECT_URL", "RedirectURL",}, {"REMOTE_ADDR", "RemoteAddress"}, {"REMOTE_HOST", "RemoteHost"}, {"REMOTE_IDENT", "RemoteIdent"}, {"REMOTE_PORT", "RemotePort"}, {"REMOTE_USER", "RemoteUser"}, {"REMOTE_GROUP", "RemoteGroup"}, {"REQUEST_METHOD", "RequestMethod"}, {"REQUEST_URI", "RequestURI"}, {"SCRIPT_FILENAME", "ScriptFilename"}, {"SCRIPT_NAME", "ScriptName"}, {"SERVER_ADDR", "ServerAddress"}, {"SERVER_ADMIN", "ServerAdmin"}, {"SERVER_NAME", "ServerName"}, {"SERVER_PORT", "ServerPort"}, {"SERVER_ROOT", "ServerRoot"}, {"SERVER_PROTOCOL", "ServerProtocol"}, {"SERVER_SOFTWARE", "ServerSoftware"}, /* SSL Vars from mod_ssl */ {"HTTPS", "HTTPS"}, {"SSL_PROTOCOL", "SSL.Protocol"}, {"SSL_SESSION_ID", "SSL.SessionID"}, {"SSL_CIPHER", "SSL.Cipher"}, {"SSL_CIPHER_EXPORT", "SSL.Cipher.Export"}, {"SSL_CIPHER_USEKEYSIZE", "SSL.Cipher.UseKeySize"}, {"SSL_CIPHER_ALGKEYSIZE", "SSL.Cipher.AlgKeySize"}, {"SSL_VERSION_INTERFACE", "SSL.Version.Interface"}, {"SSL_VERSION_LIBRARY", "SSL.Version.Library"}, {"SSL_CLIENT_M_VERSION", "SSL.Client.M.Version"}, {"SSL_CLIENT_M_SERIAL", "SSL.Client.M.Serial"}, {"SSL_CLIENT_S_DN", "SSL.Client.S.DN"}, {"SSL_CLIENT_S_DN_x509", "SSL.Client.S.DN.x509"}, {"SSL_CLIENT_I_DN", "SSL.Client.I.DN"}, {"SSL_CLIENT_I_DN_x509", "SSL.Client.I.DN.x509"}, {"SSL_CLIENT_V_START", "SSL.Client.V.Start"}, {"SSL_CLIENT_V_END", "SSL.Client.V.End"}, {"SSL_CLIENT_A_SIG", "SSL.Client.A.SIG"}, {"SSL_CLIENT_A_KEY", "SSL.Client.A.KEY"}, {"SSL_CLIENT_CERT", "SSL.Client.CERT"}, {"SSL_CLIENT_CERT_CHAINn", "SSL.Client.CERT.CHAINn"}, {"SSL_CLIENT_VERIFY", "SSL.Client.Verify"}, {"SSL_SERVER_M_VERSION", "SSL.Server.M.Version"}, {"SSL_SERVER_M_SERIAL", "SSL.Server.M.Serial"}, {"SSL_SERVER_S_DN", "SSL.Server.S.DN"}, {"SSL_SERVER_S_DN_x509", "SSL.Server.S.DN.x509"}, {"SSL_SERVER_S_DN_CN", "SSL.Server.S.DN.CN"}, {"SSL_SERVER_S_DN_EMAIL", "SSL.Server.S.DN.Email"}, {"SSL_SERVER_S_DN_O", "SSL.Server.S.DN.O"}, {"SSL_SERVER_S_DN_OU", "SSL.Server.S.DN.OU"}, {"SSL_SERVER_S_DN_C", "SSL.Server.S.DN.C"}, {"SSL_SERVER_S_DN_SP", "SSL.Server.S.DN.SP"}, {"SSL_SERVER_S_DN_L", "SSL.Server.S.DN.L"}, {"SSL_SERVER_I_DN", "SSL.Server.I.DN"}, {"SSL_SERVER_I_DN_x509", "SSL.Server.I.DN.x509"}, {"SSL_SERVER_I_DN_CN", "SSL.Server.I.DN.CN"}, {"SSL_SERVER_I_DN_EMAIL", "SSL.Server.I.DN.Email"}, {"SSL_SERVER_I_DN_O", "SSL.Server.I.DN.O"}, {"SSL_SERVER_I_DN_OU", "SSL.Server.I.DN.OU"}, {"SSL_SERVER_I_DN_C", "SSL.Server.I.DN.C"}, {"SSL_SERVER_I_DN_SP", "SSL.Server.I.DN.SP"}, {"SSL_SERVER_I_DN_L", "SSL.Server.I.DN.L"}, {"SSL_SERVER_V_START", "SSL.Server.V.Start"}, {"SSL_SERVER_V_END", "SSL.Server.V.End"}, {"SSL_SERVER_A_SIG", "SSL.Server.A.SIG"}, {"SSL_SERVER_A_KEY", "SSL.Server.A.KEY"}, {"SSL_SERVER_CERT", "SSL.Server.CERT"}, /* SSL Vars mapped from others */ /* Hmm, if we're running under mod_ssl w/ +CompatEnvVars, we set these * twice... */ {"SSL_PROTOCOL_VERSION", "SSL.Protocol"}, {"SSLEAY_VERSION", "SSL.Version.Library"}, {"HTTPS_CIPHER", "SSL.Cipher"}, {"HTTPS_EXPORT", "SSL.Cipher.Export"}, {"HTTPS_SECRETKEYSIZE", "SSL.Cipher.UseKeySize"}, {"HTTPS_KEYSIZE", "SSL.Cipher.AlgKeySize"}, {"SSL_SERVER_KEY_SIZE", "SSL.Cipher.AlgKeySize"}, {"SSL_SERVER_CERTIFICATE", "SSL.Server.CERT"}, {"SSL_SERVER_CERT_START", "SSL.Server.V.Start"}, {"SSL_SERVER_CERT_END", "SSL.Server.V.End"}, {"SSL_SERVER_CERT_SERIAL", "SSL.Server.M.Serial"}, {"SSL_SERVER_SIGNATURE_ALGORITHM", "SSL.Server.A.SIG"}, {"SSL_SERVER_DN", "SSL.Server.S.DN"}, {"SSL_SERVER_CN", "SSL.Server.S.DN.CN"}, {"SSL_SERVER_EMAIL", "SSL.Server.S.DN.Email"}, {"SSL_SERVER_O", "SSL.Server.S.DN.O"}, {"SSL_SERVER_OU", "SSL.Server.S.DN.OU"}, {"SSL_SERVER_C", "SSL.Server.S.DN.C"}, {"SSL_SERVER_SP", "SSL.Server.S.DN.SP"}, {"SSL_SERVER_L", "SSL.Server.S.DN.L"}, {"SSL_SERVER_IDN", "SSL.Server.I.DN"}, {"SSL_SERVER_ICN", "SSL.Server.I.DN.CN"}, {"SSL_SERVER_IEMAIL", "SSL.Server.I.DN.Email"}, {"SSL_SERVER_IO", "SSL.Server.I.DN.O"}, {"SSL_SERVER_IOU", "SSL.Server.I.DN.OU"}, {"SSL_SERVER_IC", "SSL.Server.I.DN.C"}, {"SSL_SERVER_ISP", "SSL.Server.I.DN.SP"}, {"SSL_SERVER_IL", "SSL.Server.I.DN.L"}, {"SSL_CLIENT_CERTIFICATE", "SSL.Client.CERT"}, {"SSL_CLIENT_CERT_START", "SSL.Client.V.Start"}, {"SSL_CLIENT_CERT_END", "SSL.Client.V.End"}, {"SSL_CLIENT_CERT_SERIAL", "SSL.Client.M.Serial"}, {"SSL_CLIENT_SIGNATURE_ALGORITHM", "SSL.Client.A.SIG"}, {"SSL_CLIENT_DN", "SSL.Client.S.DN"}, {"SSL_CLIENT_CN", "SSL.Client.S.DN.CN"}, {"SSL_CLIENT_EMAIL", "SSL.Client.S.DN.Email"}, {"SSL_CLIENT_O", "SSL.Client.S.DN.O"}, {"SSL_CLIENT_OU", "SSL.Client.S.DN.OU"}, {"SSL_CLIENT_C", "SSL.Client.S.DN.C"}, {"SSL_CLIENT_SP", "SSL.Client.S.DN.SP"}, {"SSL_CLIENT_L", "SSL.Client.S.DN.L"}, {"SSL_CLIENT_IDN", "SSL.Client.I.DN"}, {"SSL_CLIENT_ICN", "SSL.Client.I.DN.CN"}, {"SSL_CLIENT_IEMAIL", "SSL.Client.I.DN.Email"}, {"SSL_CLIENT_IO", "SSL.Client.I.DN.O"}, {"SSL_CLIENT_IOU", "SSL.Client.I.DN.OU"}, {"SSL_CLIENT_IC", "SSL.Client.I.DN.C"}, {"SSL_CLIENT_ISP", "SSL.Client.I.DN.SP"}, {"SSL_CLIENT_IL", "SSL.Client.I.DN.L"}, {"SSL_EXPORT", "SSL.Cipher.Export"}, {"SSL_KEYSIZE", "SSL.Cipher.AlgKeySize"}, {"SSL_SECKEYSIZE", "SSL.Cipher.UseKeySize"}, {"SSL_SSLEAY_VERSION", "SSL.Version.Library"}, /* Old vars not in mod_ssl */ {"SSL_STRONG_CRYPTO", "SSL.Strong.Crypto"}, {"SSL_SERVER_KEY_EXP", "SSL.Server.Key.Exp"}, {"SSL_SERVER_KEY_ALGORITHM", "SSL.Server.Key.Algorithm"}, {"SSL_SERVER_KEY_SIZE", "SSL.Server.Key.Size"}, {"SSL_SERVER_SESSIONDIR", "SSL.Server.SessionDir"}, {"SSL_SERVER_CERTIFICATELOGDIR", "SSL.Server.CertificateLogDir"}, {"SSL_SERVER_CERTFILE", "SSL.Server.CertFile"}, {"SSL_SERVER_KEYFILE", "SSL.Server.KeyFile"}, {"SSL_SERVER_KEYFILETYPE", "SSL.Server.KeyFileType"}, {"SSL_CLIENT_KEY_EXP", "SSL.Client.Key.Exp"}, {"SSL_CLIENT_KEY_ALGORITHM", "SSL.Client.Key.Algorithm"}, {"SSL_CLIENT_KEY_SIZE", "SSL.Client.Key.Size"}, {NULL, NULL} }; struct _http_vars { char *env_name; char *hdf_name; } HTTPVars[] = { {"HTTP_ACCEPT", "Accept"}, {"HTTP_ACCEPT_CHARSET", "AcceptCharset"}, {"HTTP_ACCEPT_ENCODING", "AcceptEncoding"}, {"HTTP_ACCEPT_LANGUAGE", "AcceptLanguage"}, {"HTTP_COOKIE", "Cookie"}, {"HTTP_HOST", "Host"}, {"HTTP_USER_AGENT", "UserAgent"}, {"HTTP_IF_MODIFIED_SINCE", "IfModifiedSince"}, {"HTTP_REFERER", "Referer"}, {"HTTP_VIA", "Via"}, /* SOAP */ {"HTTP_SOAPACTION", "Soap.Action"}, {NULL, NULL} }; static char *Argv0 = ""; int IgnoreEmptyFormVars = 0; 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) { NEOERR *err; char *s; err = cgiwrap_getenv (env, &s); if (err != STATUS_OK) return nerr_pass (err); if (s != NULL) { err = hdf_set_buf (cgi->hdf, name, s); if (err != STATUS_OK) { free(s); return nerr_pass (err); } } return STATUS_OK; } char *cgi_url_unescape (char *value) { int i = 0, o = 0; unsigned char *s = (unsigned char *)value; if (s == NULL) return value; while (s[i]) { if (s[i] == '+') { s[o++] = ' '; i++; } else if (s[i] == '%' && isxdigit(s[i+1]) && isxdigit(s[i+2])) { char num; num = (s[i+1] >= 'A') ? ((s[i+1] & 0xdf) - 'A') + 10 : (s[i+1] - '0'); num *= 16; num += (s[i+2] >= 'A') ? ((s[i+2] & 0xdf) - 'A') + 10 : (s[i+2] - '0'); s[o++] = num; i+=3; } else { s[o++] = s[i++]; } } if (i && o) s[o] = '\0'; return (char *)s; } NEOERR *cgi_js_escape (const char *in, char **esc) { return nerr_pass(neos_js_escape(in, esc)); } NEOERR *cgi_url_escape (const char *buf, char **esc) { return nerr_pass(neos_url_escape(buf, esc, NULL)); } NEOERR *cgi_url_escape_more (const char *in, char **esc, const char *other) { return nerr_pass(neos_url_escape(in, esc, other)); } NEOERR *cgi_url_validate (const char *buf, char **esc) { return nerr_pass(neos_url_validate(buf, esc)); } static NEOERR *_parse_query (CGI *cgi, char *query) { NEOERR *err = STATUS_OK; char *t, *k, *v, *l; char buf[256]; char unnamed[10]; int unnamed_count = 0; HDF *obj, *child; if (query && *query) { k = strtok_r(query, "&", &l); while (k && *k) { v = strchr(k, '='); if (v == NULL) { v = ""; } else { *v = '\0'; v++; } /* Check for some invalid query strings */ if (*k == 0) { /* '?=foo' gets mapped in as Query._1=foo */ snprintf(unnamed,sizeof(unnamed), "_%d", unnamed_count++); k = unnamed; } else if (*k == '.') { /* an hdf element can't start with a period */ *k = '_'; } snprintf(buf, sizeof(buf), "Query.%s", cgi_url_unescape(k)); if (!(cgi->ignore_empty_form_vars && (*v == '\0'))) { cgi_url_unescape(v); obj = hdf_get_obj (cgi->hdf, buf); if (obj != NULL) { int i = 0; char buf2[10]; child = hdf_obj_child (obj); if (child == NULL) { t = hdf_obj_value (obj); err = hdf_set_value (obj, "0", t); if (err != STATUS_OK) break; i = 1; } else { while (child != NULL) { i++; child = hdf_obj_next (child); if (err != STATUS_OK) break; } if (err != STATUS_OK) break; } snprintf (buf2, sizeof(buf2), "%d", i); err = hdf_set_value (obj, buf2, v); if (err != STATUS_OK) break; } err = hdf_set_value (cgi->hdf, buf, v); if (nerr_match(err, NERR_ASSERT)) { STRING str; string_init(&str); nerr_error_string(err, &str); ne_warn("Unable to set Query value: %s = %s: %s", buf, v, str.buf); string_clear(&str); nerr_ignore(&err); } if (err != STATUS_OK) break; } k = strtok_r(NULL, "&", &l); } } return nerr_pass(err); } /* Is it an error if its a short read? */ static NEOERR *_parse_post_form (CGI *cgi) { NEOERR *err = STATUS_OK; char *l, *query; int len, r, o; l = hdf_get_value (cgi->hdf, "CGI.ContentLength", NULL); if (l == NULL) return STATUS_OK; len = atoi (l); if (len <= 0) return STATUS_OK; cgi->data_expected = len; query = (char *) malloc (sizeof(char) * (len + 1)); if (query == NULL) return nerr_raise (NERR_NOMEM, "Unable to allocate memory to read POST input of length %d", len); o = 0; while (o < len) { cgiwrap_read (query + o, len - o, &r); if (r <= 0) break; o = o + r; } if (r < 0) { free(query); return nerr_raise_errno (NERR_IO, "Short read on CGI POST input (%d < %d)", o, len); } if (o != len) { free(query); return nerr_raise (NERR_IO, "Short read on CGI POST input (%d < %d)", o, len); } query[len] = '\0'; err = _parse_query (cgi, query); free(query); return nerr_pass(err); } static NEOERR *_parse_cookie (CGI *cgi) { NEOERR *err; char *cookie; char *k, *v, *l; HDF *obj; err = hdf_get_copy (cgi->hdf, "HTTP.Cookie", &cookie, NULL); if (err != STATUS_OK) return nerr_pass(err); if (cookie == NULL) return STATUS_OK; err = hdf_set_value (cgi->hdf, "Cookie", cookie); if (err != STATUS_OK) { free(cookie); return nerr_pass(err); } obj = hdf_get_obj (cgi->hdf, "Cookie"); k = l = cookie; while (*l && *l != '=' && *l != ';') l++; while (*k) { if (*l == '=') { if (*l) *l++ = '\0'; v = l; while (*l && *l != ';') l++; if (*l) *l++ = '\0'; } else { v = ""; if (*l) *l++ = '\0'; } k = neos_strip (k); v = neos_strip (v); if (k[0] && v[0]) { err = hdf_set_value (obj, k, v); if (nerr_match(err, NERR_ASSERT)) { STRING str; string_init(&str); nerr_error_string(err, &str); ne_warn("Unable to set Cookie value: %s = %s: %s", k, v, str.buf); string_clear(&str); nerr_ignore(&err); } if (err) break; } k = l; while (*l && *l != '=' && *l != ';') l++; } free (cookie); return nerr_pass(err); } #ifdef ENABLE_REMOTE_DEBUG static void _launch_debugger (CGI *cgi, char *display) { pid_t myPid, pid; char buffer[127]; char *debugger; HDF *obj; char *allowed; /* Only allow remote debugging from allowed hosts */ for (obj = hdf_get_child (cgi->hdf, "Config.Displays"); obj; obj = hdf_obj_next (obj)) { allowed = hdf_obj_value (obj); if (allowed && !strcmp (display, allowed)) break; } if (obj == NULL) return; myPid = getpid(); if ((pid = fork()) < 0) return; if ((debugger = hdf_get_value (cgi->hdf, "Config.Debugger", NULL)) == NULL) { debugger = "/usr/local/bin/sudo /usr/local/bin/ddd -display %s %s %d"; } if (!pid) { sprintf(buffer, debugger, display, Argv0, myPid); execl("/bin/sh", "sh", "-c", buffer, NULL); } else { sleep(60); } } #endif static NEOERR *cgi_pre_parse (CGI *cgi) { NEOERR *err; int x = 0; char buf[256]; char *query; while (CGIVars[x].env_name) { snprintf (buf, sizeof(buf), "CGI.%s", CGIVars[x].hdf_name); err = _add_cgi_env_var(cgi, CGIVars[x].env_name, buf); if (err != STATUS_OK) return nerr_pass (err); x++; } x = 0; while (HTTPVars[x].env_name) { snprintf (buf, sizeof(buf), "HTTP.%s", HTTPVars[x].hdf_name); err = _add_cgi_env_var(cgi, HTTPVars[x].env_name, buf); if (err != STATUS_OK) return nerr_pass (err); x++; } err = _parse_cookie(cgi); if (err != STATUS_OK) return nerr_pass (err); err = hdf_get_copy (cgi->hdf, "CGI.QueryString", &query, NULL); if (err != STATUS_OK) return nerr_pass (err); if (query != NULL) { err = _parse_query(cgi, query); free(query); if (err != STATUS_OK) return nerr_pass (err); } { char *d = hdf_get_value(cgi->hdf, "Query.debug_pause", NULL); char *d_p = hdf_get_value(cgi->hdf, "Config.DebugPassword", NULL); if (hdf_get_int_value(cgi->hdf, "Config.DebugEnabled", 0) && d && d_p && !strcmp(d, d_p)) { sleep(20); } } #ifdef ENABLE_REMOTE_DEBUG { char *display; display = hdf_get_value (cgi->hdf, "Query.xdisplay", NULL); if (display) { fprintf(stderr, "** Got display %s\n", display); _launch_debugger(cgi, display); } } #endif return STATUS_OK; } NEOERR *cgi_register_parse_cb(CGI *cgi, const char *method, const 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) { if (my_pcb->method != NULL) free(my_pcb->method); if (my_pcb->ctype != NULL) free(my_pcb->ctype); 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")) { err = _parse_post_form(cgi); if (err != STATUS_OK) return nerr_pass (err); } else if (type && !strncmp (type, "multipart/form-data", 19)) { err = parse_rfc2388 (cgi); if (err != STATUS_OK) return nerr_pass (err); } #if 0 else { int len, x, r; char *l; char buf[4096]; FILE *fp; fp = fopen("/tmp/upload", "w"); l = hdf_get_value (cgi->hdf, "CGI.ContentLength", NULL); if (l == NULL) return STATUS_OK; len = atoi (l); x = 0; while (x < len) { if (len-x > sizeof(buf)) cgiwrap_read (buf, sizeof(buf), &r); else cgiwrap_read (buf, len - x, &r); fwrite (buf, 1, r, fp); x += r; } fclose (fp); if (err) return nerr_pass(err); } #endif } else if (!strcmp(method, "PUT")) { FILE *fp; int len, x, r, w; char *l; char buf[4096]; int unlink_files = hdf_get_int_value(cgi->hdf, "Config.Upload.Unlink", 1); err = open_upload(cgi, unlink_files, &fp); if (err) return nerr_pass(err); l = hdf_get_value (cgi->hdf, "CGI.ContentLength", NULL); if (l == NULL) return STATUS_OK; len = atoi (l); if (len <= 0) return STATUS_OK; x = 0; while (x < len) { if (len-x > sizeof(buf)) cgiwrap_read (buf, sizeof(buf), &r); else cgiwrap_read (buf, len - x, &r); w = fwrite (buf, sizeof(char), r, fp); if (w != r) { err = nerr_raise_errno(NERR_IO, "Short write on PUT: %d < %d", w, r); break; } x += r; } if (err) return nerr_pass(err); fseek(fp, 0, SEEK_SET); l = hdf_get_value(cgi->hdf, "CGI.PathInfo", NULL); if (l) err = hdf_set_value (cgi->hdf, "PUT", l); if (err) return nerr_pass(err); if (type) err = hdf_set_value (cgi->hdf, "PUT.Type", type); if (err) return nerr_pass(err); err = hdf_set_int_value (cgi->hdf, "PUT.FileHandle", uListLength(cgi->files)); if (err) return nerr_pass(err); if (!unlink_files) { char *name; err = uListGet(cgi->filenames, uListLength(cgi->filenames)-1, (void *)&name); if (err) return nerr_pass(err); err = hdf_set_value (cgi->hdf, "PUT.FileName", name); if (err) return nerr_pass(err); } } return STATUS_OK; } NEOERR *cgi_init (CGI **cgi, HDF *hdf) { NEOERR *err = STATUS_OK; CGI *mycgi; if (ExceptionsInit == 0) { err = nerr_init(); if (err) return nerr_pass(err); err = nerr_register(&CGIFinished, "CGIFinished"); 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; } *cgi = NULL; mycgi = (CGI *) calloc (1, sizeof(CGI)); if (mycgi == NULL) return nerr_raise(NERR_NOMEM, "Unable to allocate space for CGI"); mycgi->time_start = ne_timef(); mycgi->ignore_empty_form_vars = IgnoreEmptyFormVars; do { if (hdf == NULL) { err = hdf_init (&(mycgi->hdf)); if (err != STATUS_OK) break; } else { mycgi->hdf = hdf; } err = cgi_pre_parse (mycgi); if (err != STATUS_OK) break; } while (0); if (err == STATUS_OK) *cgi = mycgi; else { cgi_destroy(&mycgi); } return nerr_pass(err); } static void _destroy_tmp_file(char *filename) { unlink(filename); free(filename); } void cgi_destroy (CGI **cgi) { CGI *my_cgi; if (!cgi || !*cgi) return; my_cgi = *cgi; if (my_cgi->hdf) hdf_destroy (&(my_cgi->hdf)); if (my_cgi->buf) free(my_cgi->buf); if (my_cgi->files) uListDestroyFunc(&(my_cgi->files), (void (*)(void *))fclose); if (my_cgi->filenames) uListDestroyFunc(&(my_cgi->filenames), (void (*)(void *))_destroy_tmp_file); free (*cgi); *cgi = NULL; } static NEOERR *render_cb (void *ctx, char *buf) { STRING *str = (STRING *)ctx; NEOERR *err; err = nerr_pass(string_append(str, buf)); return err; } static NEOERR *cgi_headers (CGI *cgi) { NEOERR *err = STATUS_OK; HDF *obj, *child; char *s, *charset = NULL; if (hdf_get_int_value (cgi->hdf, "Config.NoCache", 0)) { /* Ok, we try really hard to defeat caches here */ /* this isn't in any HTTP rfc's, it just seems to be a convention */ err = cgiwrap_writef ("Pragma: no-cache\r\n"); if (err != STATUS_OK) return nerr_pass (err); err = cgiwrap_writef ("Expires: Fri, 01 Jan 1990 00:00:00 GMT\r\n"); if (err != STATUS_OK) return nerr_pass (err); err = cgiwrap_writef ("Cache-control: no-cache, must-revalidate, no-cache=\"Set-Cookie\", private\r\n"); if (err != STATUS_OK) return nerr_pass (err); } obj = hdf_get_obj (cgi->hdf, "cgiout"); if (obj) { s = hdf_get_value (obj, "Status", NULL); if (s) err = cgiwrap_writef ("Status: %s\r\n", s); if (err != STATUS_OK) return nerr_pass (err); s = hdf_get_value (obj, "Location", NULL); if (s) err = cgiwrap_writef ("Location: %s\r\n", s); if (err != STATUS_OK) return nerr_pass (err); child = hdf_get_obj (cgi->hdf, "cgiout.other"); if (child) { child = hdf_obj_child (child); while (child != NULL) { s = hdf_obj_value (child); err = cgiwrap_writef ("%s\r\n", s); if (err != STATUS_OK) return nerr_pass (err); child = hdf_obj_next(child); } } charset = hdf_get_value (obj, "charset", NULL); s = hdf_get_value (obj, "ContentType", "text/html"); if (charset) err = cgiwrap_writef ("Content-Type: %s; charset=%s\r\n\r\n", s, charset); else err = cgiwrap_writef ("Content-Type: %s\r\n\r\n", s); if (err != STATUS_OK) return nerr_pass (err); } else { /* Default */ err = cgiwrap_writef ("Content-Type: text/html\r\n\r\n"); if (err != STATUS_OK) return nerr_pass (err); } return STATUS_OK; } #if defined(HTML_COMPRESSION) /* Copy these here from zutil.h, which we aren't supposed to include */ #define DEF_MEM_LEVEL 8 #define OS_CODE 0x03 static NEOERR *cgi_compress (STRING *str, char *obuf, int *olen) { z_stream stream; int err; stream.next_in = (Bytef*)str->buf; stream.avail_in = (uInt)str->len; stream.next_out = (Bytef*)obuf; stream.avail_out = (uInt)*olen; if ((uLong)stream.avail_out != *olen) return nerr_raise(NERR_NOMEM, "Destination too big: %d", *olen); stream.zalloc = (alloc_func)0; stream.zfree = (free_func)0; stream.opaque = (voidpf)0; /* err = deflateInit(&stream, Z_DEFAULT_COMPRESSION); */ err = deflateInit2(&stream, Z_DEFAULT_COMPRESSION, Z_DEFLATED, -MAX_WBITS, DEF_MEM_LEVEL, Z_DEFAULT_STRATEGY); if (err != Z_OK) return nerr_raise(NERR_SYSTEM, "deflateInit2 returned %d", err); err = deflate(&stream, Z_FINISH); if (err != Z_STREAM_END) { deflateEnd(&stream); return nerr_raise(NERR_SYSTEM, "deflate returned %d", err); } *olen = stream.total_out; err = deflateEnd(&stream); return STATUS_OK; } #endif /* This ws strip function is Dave's version, designed to make debug * easier, and the output a bit smaller... but not as small as it could * be: essentially, it strips all empty lines, all extra space at the * end of the line, except in pre/textarea tags. * * Ok, expanding to 3 levels: * 0 - No stripping * 1 - Dave's debug stripper (as above) * 2 - strip all extra white space * * We don't currently strip white space in a tag * * */ #if 0 static void debug_output(char *header, char *s, int n) { int x; fprintf (stderr, "%s ", header); for (x = 0; x < n; x++) { fprintf (stderr, "%c", s[x]); } fprintf(stderr, "\n"); } #endif void cgi_html_ws_strip(STRING *str, int level) { int ws = 0; int seen_nonws = level > 1; int i, o, l; char *ch; i = o = 0; if (str->len) { ws = isspace(str->buf[0]); } while (i < str->len) { if (str->buf[i] == '<') { str->buf[o++] = str->buf[i++]; if (!strncasecmp(str->buf + i, "textarea", 8)) { ch = str->buf + i; do { ch = strchr(ch, '<'); if (ch == NULL) { memmove(str->buf + o, str->buf + i, str->len - i); str->len = o + str->len - i; str->buf[str->len] = '\0'; return; } ch++; } while (strncasecmp(ch, "/textarea>", 10)); ch += 10; l = ch - str->buf - i; memmove(str->buf + o, str->buf + i, l); o += l; i += l; } else if (!strncasecmp(str->buf + i, "pre", 3)) { ch = str->buf + i; do { ch = strchr(ch, '<'); if (ch == NULL) { memmove(str->buf + o, str->buf + i, str->len - i); str->len = o + str->len - i; str->buf[str->len] = '\0'; return; } ch++; } while (strncasecmp(ch, "/pre>", 5)); ch += 5; l = ch - str->buf - i; memmove(str->buf + o, str->buf + i, l); o += l; i += l; } else { ch = strchr(str->buf + i, '>'); if (ch == NULL) { memmove(str->buf + o, str->buf + i, str->len - i); str->len = o + str->len - i; str->buf[str->len] = '\0'; return; } ch++; /* debug_output("copying tag", str->buf + i, ch - str->buf - i); * */ l = ch - str->buf - i; memmove(str->buf + o, str->buf + i, l); o += l; i += l; } /* debug_output("result", str->buf, o); */ seen_nonws = 1; ws = 0; } else if (str->buf[i] == '\n') { /* This has the effect of erasing all whitespace at the eol plus * erasing all blank lines */ while (o && isspace(str->buf[o-1])) o--; str->buf[o++] = str->buf[i++]; ws = level > 1; seen_nonws = level > 1; } else if (seen_nonws && isspace(str->buf[i])) { if (ws) { i++; } else { str->buf[o++] = str->buf[i++]; ws = 1; } } else { seen_nonws = 1; ws = 0; str->buf[o++] = str->buf[i++]; } } str->len = o; str->buf[str->len] = '\0'; } NEOERR *cgi_output (CGI *cgi, STRING *str) { NEOERR *err = STATUS_OK; double dis; int is_html = 0; int use_deflate = 0; int use_gzip = 0; int do_debug = 0; int do_timefooter = 0; int ws_strip_level = 0; char *s, *e; s = hdf_get_value (cgi->hdf, "Query.debug", NULL); e = hdf_get_value (cgi->hdf, "Config.DebugPassword", NULL); if (hdf_get_int_value(cgi->hdf, "Config.DebugEnabled", 0) && s && e && !strcmp(s, e)) do_debug = 1; do_timefooter = hdf_get_int_value (cgi->hdf, "Config.TimeFooter", 1); ws_strip_level = hdf_get_int_value (cgi->hdf, "Config.WhiteSpaceStrip", 1); dis = ne_timef(); s = hdf_get_value (cgi->hdf, "cgiout.ContentType", "text/html"); if (!strcasecmp(s, "text/html")) is_html = 1; #if defined(HTML_COMPRESSION) /* Determine whether or not we can compress the output */ if (is_html && hdf_get_int_value (cgi->hdf, "Config.CompressionEnabled", 0)) { err = hdf_get_copy (cgi->hdf, "HTTP.AcceptEncoding", &s, NULL); if (err != STATUS_OK) return nerr_pass (err); if (s) { char *next; e = strtok_r (s, ",", &next); while (e && !use_deflate) { if (strstr(e, "deflate") != NULL) { use_deflate = 1; use_gzip = 0; } else if (strstr(e, "gzip") != NULL) use_gzip = 1; e = strtok_r (NULL, ",", &next); } free (s); } s = hdf_get_value (cgi->hdf, "HTTP.UserAgent", NULL); if (s) { if (strstr(s, "MSIE 4") || strstr(s, "MSIE 5") || strstr(s, "MSIE 6")) { e = hdf_get_value (cgi->hdf, "HTTP.Accept", NULL); if (e && !strcmp(e, "*/*")) { use_deflate = 0; use_gzip = 0; } } else { if (strncasecmp(s, "mozilla/5.", 10)) { use_deflate = 0; use_gzip = 0; } } } else { use_deflate = 0; use_gzip = 0; } if (use_deflate) { err = hdf_set_value (cgi->hdf, "cgiout.other.encoding", "Content-Encoding: deflate"); } else if (use_gzip) { err = hdf_set_value (cgi->hdf, "cgiout.other.encoding", "Content-Encoding: gzip"); } if (err != STATUS_OK) return nerr_pass(err); } #endif err = cgi_headers(cgi); if (err != STATUS_OK) return nerr_pass(err); if (is_html) { char buf[50]; int x; if (do_timefooter) { snprintf (buf, sizeof(buf), "\n<!-- %5.3f:%d -->\n", dis - cgi->time_start, use_deflate || use_gzip); err = string_append (str, buf); if (err != STATUS_OK) return nerr_pass(err); } if (ws_strip_level) { cgi_html_ws_strip(str, ws_strip_level); } if (do_debug) { err = string_append (str, "<hr>"); if (err != STATUS_OK) return nerr_pass(err); x = 0; while (1) { char *k, *v; err = cgiwrap_iterenv (x, &k, &v); if (err != STATUS_OK) return nerr_pass(err); if (k == NULL) break; err =string_appendf (str, "%s = %s<br>", k, v); if (err != STATUS_OK) return nerr_pass(err); free(k); free(v); x++; } err = string_append (str, "<pre>"); if (err != STATUS_OK) return nerr_pass(err); err = hdf_dump_str (cgi->hdf, NULL, 0, str); if (err != STATUS_OK) return nerr_pass(err); } } #if defined(HTML_COMPRESSION) if (is_html && (use_deflate || use_gzip)) { char *dest; static int gz_magic[2] = {0x1f, 0x8b}; /* gzip magic header */ char gz_buf[20]; /* gzip header/footer buffer, len of header is 10 bytes */ unsigned int crc = 0; int len2; if (use_gzip) { crc = crc32(0L, Z_NULL, 0); crc = crc32(crc, (const Bytef *)(str->buf), str->len); } len2 = str->len * 2; dest = (char *) malloc (sizeof(char) * len2); if (dest != NULL) { do { err = cgi_compress (str, dest, &len2); if (err == STATUS_OK) { if (use_gzip) { /* I'm using sprintf instead of cgiwrap_writef since * the wrapper writef might not handle values with * embedded NULLs... though I should fix the python one * now as well */ sprintf(gz_buf, "%c%c%c%c%c%c%c%c%c%c", gz_magic[0], gz_magic[1], Z_DEFLATED, 0 /*flags*/, 0,0,0,0 /*time*/, 0 /*xflags*/, OS_CODE); err = cgiwrap_write(gz_buf, 10); } if (err != STATUS_OK) break; err = cgiwrap_write(dest, len2); if (err != STATUS_OK) break; if (use_gzip) { /* write crc and len in network order */ sprintf(gz_buf, "%c%c%c%c%c%c%c%c", (0xff & (crc >> 0)), (0xff & (crc >> 8)), (0xff & (crc >> 16)), (0xff & (crc >> 24)), (0xff & (str->len >> 0)), (0xff & (str->len >> 8)), (0xff & (str->len >> 16)), (0xff & (str->len >> 24))); err = cgiwrap_write(gz_buf, 8); if (err != STATUS_OK) break; } } else { nerr_log_error (err); err = cgiwrap_write(str->buf, str->len); } } while (0); free (dest); } else { err = cgiwrap_write(str->buf, str->len); } } else #endif { err = cgiwrap_write(str->buf, str->len); } return nerr_pass(err); } NEOERR *cgi_html_escape_strfunc(const char *str, char **ret) { return nerr_pass(html_escape_alloc(str, strlen(str), ret)); } NEOERR *cgi_html_strip_strfunc(const char *str, char **ret) { return nerr_pass(html_strip_alloc(str, strlen(str), ret)); } NEOERR *cgi_text_html_strfunc(const char *str, char **ret) { return nerr_pass(convert_text_html_alloc(str, strlen(str), ret)); } NEOERR *cgi_register_strfuncs(CSPARSE *cs) { NEOERR *err; err = cs_register_esc_strfunc(cs, "url_escape", cgi_url_escape); if (err != STATUS_OK) return nerr_pass(err); err = cs_register_esc_strfunc(cs, "html_escape", cgi_html_escape_strfunc); if (err != STATUS_OK) return nerr_pass(err); err = cs_register_strfunc(cs, "text_html", cgi_text_html_strfunc); if (err != STATUS_OK) return nerr_pass(err); err = cs_register_esc_strfunc(cs, "js_escape", cgi_js_escape); if (err != STATUS_OK) return nerr_pass(err); err = cs_register_strfunc(cs, "html_strip", cgi_html_strip_strfunc); if (err != STATUS_OK) return nerr_pass(err); err = cs_register_esc_strfunc(cs, "url_validate", cgi_url_validate); if (err != STATUS_OK) return nerr_pass(err); return STATUS_OK; } NEOERR *cgi_cs_init(CGI *cgi, CSPARSE **cs) { NEOERR *err; *cs = NULL; do { err = cs_init (cs, cgi->hdf); if (err != STATUS_OK) break; err = cgi_register_strfuncs(*cs); if (err != STATUS_OK) break; } while (0); if (err && *cs) cs_destroy(cs); return nerr_pass(err); } NEOERR *cgi_display (CGI *cgi, const char *cs_file) { NEOERR *err = STATUS_OK; char *debug; CSPARSE *cs = NULL; STRING str; int do_dump = 0; char *t; string_init(&str); debug = hdf_get_value (cgi->hdf, "Query.debug", NULL); t = hdf_get_value (cgi->hdf, "Config.DumpPassword", NULL); if (hdf_get_int_value(cgi->hdf, "Config.DebugEnabled", 0) && debug && t && !strcmp (debug, t)) do_dump = 1; do { err = cs_init (&cs, cgi->hdf); if (err != STATUS_OK) break; err = cgi_register_strfuncs(cs); if (err != STATUS_OK) break; err = cs_parse_file (cs, cs_file); if (err != STATUS_OK) break; if (do_dump) { cgiwrap_writef("Content-Type: text/plain\n\n"); hdf_dump_str(cgi->hdf, "", 0, &str); cs_dump(cs, &str, render_cb); cgiwrap_writef("%s", str.buf); break; } else { err = cs_render (cs, &str, render_cb); if (err != STATUS_OK) break; } err = cgi_output(cgi, &str); if (err != STATUS_OK) break; } while (0); cs_destroy(&cs); string_clear (&str); return nerr_pass(err); } void cgi_neo_error (CGI *cgi, NEOERR *err) { STRING str; string_init(&str); cgiwrap_writef("Status: 500\n"); cgiwrap_writef("Content-Type: text/html\n\n"); cgiwrap_writef("<html><body>\nAn error occured:<pre>"); nerr_error_traceback(err, &str); cgiwrap_write(str.buf, str.len); cgiwrap_writef("</pre></body></html>\n"); } void cgi_error (CGI *cgi, const char *fmt, ...) { va_list ap; cgiwrap_writef("Status: 500\n"); cgiwrap_writef("Content-Type: text/html\n\n"); cgiwrap_writef("<html><body>\nAn error occured:<pre>"); va_start (ap, fmt); cgiwrap_writevf (fmt, ap); va_end (ap); cgiwrap_writef("</pre></body></html>\n"); } void cgi_debug_init (int argc, char **argv) { FILE *fp; char line[4096]; char *v, *k; Argv0 = argv[0]; if (argc) { fp = fopen(argv[1], "r"); if (fp == NULL) return; while (fgets(line, sizeof(line), fp) != NULL) { v = strchr(line, '='); if (v != NULL) { *v = '\0'; v = neos_strip(v+1); k = neos_strip(line); cgiwrap_putenv (line, v); } } fclose(fp); } } void cgi_vredirect (CGI *cgi, int uri, const char *fmt, va_list ap) { cgiwrap_writef ("Status: 302\r\n"); cgiwrap_writef ("Content-Type: text/html\r\n"); cgiwrap_writef ("Pragma: no-cache\r\n"); cgiwrap_writef ("Expires: Fri, 01 Jan 1999 00:00:00 GMT\r\n"); cgiwrap_writef ("Cache-control: no-cache, no-cache=\"Set-Cookie\", private\r\n"); if (uri) { cgiwrap_writef ("Location: "); } else { char *host; int https = 0; if (!strcmp(hdf_get_value(cgi->hdf, "CGI.HTTPS", "off"), "on")) { https = 1; } host = hdf_get_value (cgi->hdf, "HTTP.Host", NULL); if (host == NULL) host = hdf_get_value (cgi->hdf, "CGI.ServerName", "localhost"); cgiwrap_writef ("Location: %s://%s", https ? "https" : "http", host); if ((strchr(host, ':') == NULL)) { int port = hdf_get_int_value(cgi->hdf, "CGI.ServerPort", 80); if (!((https && port == 443) || (!https && port == 80))) { cgiwrap_writef(":%d", port); } } } cgiwrap_writevf (fmt, ap); cgiwrap_writef ("\r\n\r\n"); cgiwrap_writef ("Redirect page<br><br>\n"); #if 0 /* Apparently this crashes on some computers... I don't know if its * legal to reuse the va_list */ cgiwrap_writef (" Destination: <A HREF=\""); cgiwrap_writevf (fmt, ap); cgiwrap_writef ("\">"); cgiwrap_writevf (fmt, ap); cgiwrap_writef ("</A><BR>\n<BR>\n"); #endif cgiwrap_writef ("There is nothing to see here, please move along..."); } void cgi_redirect (CGI *cgi, const char *fmt, ...) { va_list ap; va_start(ap, fmt); cgi_vredirect (cgi, 0, fmt, ap); va_end(ap); return; } void cgi_redirect_uri (CGI *cgi, const char *fmt, ...) { va_list ap; va_start(ap, fmt); cgi_vredirect (cgi, 1, fmt, ap); va_end(ap); return; } char *cgi_cookie_authority (CGI *cgi, const char *host) { HDF *obj; char *domain; int hlen = 0, dlen = 0; if (host == NULL) { host = hdf_get_value (cgi->hdf, "HTTP.Host", NULL); } if (host == NULL) return NULL; while (host[hlen] && host[hlen] != ':') hlen++; obj = hdf_get_obj (cgi->hdf, "CookieAuthority"); if (obj == NULL) return NULL; for (obj = hdf_obj_child (obj); obj; obj = hdf_obj_next (obj)) { domain = hdf_obj_value (obj); dlen = strlen(domain); if (hlen >= dlen) { if (!strncasecmp (host + hlen - dlen, domain, dlen)) return domain; } } return NULL; } /* For more information about Cookies, see: * The original Netscape Cookie Spec: * http://wp.netscape.com/newsref/std/cookie_spec.html * * HTTP State Management Mechanism * http://www.ietf.org/rfc/rfc2109.txt */ NEOERR *cgi_cookie_set (CGI *cgi, const char *name, const char *value, const char *path, const char *domain, const char *time_str, int persistent, int secure) { NEOERR *err; STRING str; char my_time[256]; if (path == NULL) path = "/"; string_init(&str); do { err = string_appendf(&str, "Set-Cookie: %s=%s; path=%s", name, value, path); if (err) break; if (persistent) { if (time_str == NULL) { /* Expires in one year */ time_t exp_date = time(NULL) + 31536000; strftime (my_time, 48, "%A, %d-%b-%Y 23:59:59 GMT", gmtime (&exp_date)); time_str = my_time; } err = string_appendf(&str, "; expires=%s", time_str); if (err) break; } if (domain) { err = string_appendf(&str, "; domain=%s", domain); if (err) break; } if (secure) { err = string_append(&str, "; secure"); if (err) break; } err = string_append(&str, "\r\n"); } while (0); if (err) { string_clear(&str); return nerr_pass(err); } cgiwrap_write(str.buf, str.len); string_clear(&str); return STATUS_OK; } /* This will actually issue up to three set cookies, attempting to clear * the damn thing. */ NEOERR *cgi_cookie_clear (CGI *cgi, const char *name, const char *domain, const char *path) { if (path == NULL) path = "/"; if (domain) { if (domain[0] == '.') { cgiwrap_writef ("Set-Cookie: %s=; path=%s; domain=%s;" "expires=Thursday, 01-Jan-1970 00:00:00 GMT\r\n", name, path, domain + 1); } cgiwrap_writef("Set-Cookie: %s=; path=%s; domain=%s;" "expires=Thursday, 01-Jan-1970 00:00:00 GMT\r\n", name, path, domain); } cgiwrap_writef("Set-Cookie: %s=; path=%s; " "expires=Thursday, 01-Jan-1970 00:00:00 GMT\r\n", name, path); return STATUS_OK; }