/* * SPL - The SPL Programming Language * Copyright (C) 2004, 2005 Clifford Wolf * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * mod_cgi.c: Module for CGI namespace */ /** * Module for writing CGIs */ #include #include #include #include #include #include #include #include #include #include "spl.h" #include "compat.h" #include "webspl_common.h" extern void SPL_ABI(spl_mod_cgi_init)(struct spl_vm *vm, struct spl_module *mod, int restore); extern void SPL_ABI(spl_mod_cgi_done)(struct spl_vm *vm, struct spl_module *mod); #define auto_printf(...) \ do { \ if (ctx && ctx->outfile) \ fprintf(ctx->outfile, __VA_ARGS__); \ else \ printf(__VA_ARGS__); \ } while (0) static char *get_param(struct cgi_params_t *p, const char *name) { while (p) { if (!strcmp(p->key, name)) return p->value; p = p->next; } return 0; } static char *get_cookie(struct cgi_cookie_t *c, const char *name) { while (c) { if (!strcmp(c->key, name)) return c->value; c = c->next; } return 0; } static char *url_decode(char *s) { int size = 0; char *ret; for (int i=0; s[i]; i++, size++) if (s[i] == '%' && s[i+1] && s[i+2]) i+=2; ret = malloc(size+1); int j=0; for (int i=0; s[i]; i++, j++) { if ( s[i] == '+' ) ret[j] = ' '; else if (s[i] == '%' && s[i+1] && s[i+2]) { char num[3] = { s[i+1], s[i+2], 0 }; ret[j] = strtol(num, 0, 16); i += 2; } else if ( s[i] == '+' ) ret[j] = ' '; else ret[j] = s[i]; if (ret[j] == '\r') j--; } assert(j <= size); ret[j] = 0; return ret; } static void parse_query_string_multiformdat(struct cgi_context *ctx, char *data, int data_len, char *boundary) { char *data_end = data + data_len; char *real_boundary; // boundary is prefixed with '\r\n--' my_asprintf(&real_boundary, "\r\n--%s", boundary); // find first element data = strstr(data, boundary); if (!data) return; while (*data && data < data_end) { char *paraname = 0; char *filename = 0; // skip boundary marker data += strlen(boundary); if (*data == '\r') data++; if (*data == '\n') data++; // parse headers while (1) { if (*data == '\r') data++; char *stop = strchr(data, '\n'); if (!stop || stop == data) break; if (strncasecmp(data, "content-disposition:", strlen("content-disposition:"))) goto parse_next_header; data += strlen("content-disposition:"); data += strspn(data, " \t"); while (data < stop) { char *data_start = data; data += strspn(data, " \t"); char *n = data; int n_len = strcspn(data, "=;\n"); data += n_len; if (*data == '=') data++; if (*data == '"') { data++; char *v = data; int v_len = strcspn(data, "\"\n"); data += v_len; if (*data == '"') data++; if (!strncasecmp(n, "name", n_len)) { if (paraname) free(paraname); paraname = my_strndup(v, v_len); } if (!strncasecmp(n, "filename", n_len)) { if (filename) free(filename); filename = my_strndup(v, v_len); } } if (*data == ';') data++; if (data == data_start) break; } parse_next_header: data = stop+1; } // extract data if (*data == '\n') data++; char *stop = my_memmem(data, data_end - data, real_boundary, strlen(real_boundary)); if (!stop) stop = data_end; int part_len = stop - data; // store in cgi ctx if (paraname) { struct cgi_params_t *p = calloc(1, sizeof(struct cgi_params_t)); p->key = paraname; if (filename) { p->value = filename; p->file_data = data; p->file_size = part_len; } else { p->value = malloc(part_len+1); memcpy(p->value, data, part_len); p->value[part_len] = 0; } if (spl_utf8_check(p->value)) { char *old_value = p->value; p->value = spl_utf8_import(old_value, "latin_1"); free(old_value); /* this should never happen */ if (!p->value) p->value = strdup(""); } p->next = ctx->params; ctx->params = p; } else { free(paraname); if (filename) free(filename); } // next element data = stop + 4; } free(real_boundary); } static void parse_query_string(struct cgi_context *ctx, char *data, int data_len, char *type) { if (type) ctx->post_type = strdup(type); if (type && !strncasecmp(type, "text/", 5)) { ctx->post_data = strdup(data); return; } if (type && !strncasecmp(type, "multipart/form-data;", strlen("multipart/form-data;"))) { char *t = strstr(type, "boundary="); if (t) { t += strlen("boundary="); char *boundary = my_strndup(t, strcspn(t, " \t\n")); if (*boundary) { if (data_len < 0) data_len = strlen(data); parse_query_string_multiformdat(ctx, data, data_len, boundary); } free(boundary); } return; } char *my_query_string = strdup(data); char *token = 0, *qsbuffer = my_query_string; while ( (token=my_strsep(&qsbuffer, "&")) ) { struct cgi_params_t *p = calloc(1, sizeof(struct cgi_params_t)); char *enc_value = strchr(token, '='); if (!enc_value) { p->key = url_decode(token); p->value = strdup(p->key); } else { *(enc_value++) = 0; p->key = url_decode(token); p->value = url_decode(enc_value); } if (spl_utf8_check(p->value)) { char *old_value = p->value; p->value = spl_utf8_import(old_value, "latin_1"); free(old_value); /* this should never happen */ if (!p->value) p->value = strdup(""); } p->next = ctx->params; ctx->params = p; } free(my_query_string); } static void parse_cookie_string(struct cgi_context *ctx, char *cookie_string) { while (*cookie_string) { int n_len = strcspn(cookie_string, "=;"); char *n = malloc(n_len+1); snprintf(n, n_len+1, "%.*s", n_len, cookie_string); cookie_string += n_len; while (*cookie_string == '=') cookie_string++; int v_len = strcspn(cookie_string, ";"); char *v = malloc(v_len+1); snprintf(v, v_len+1, "%.*s", v_len, cookie_string); cookie_string += v_len; while (*cookie_string == ';' || *cookie_string == ' ') cookie_string++; struct cgi_cookie_t *c = malloc(sizeof(struct cgi_cookie_t)); c->next = ctx->cookies; c->key = n; c->value = v; ctx->cookies = c; if (spl_utf8_check(c->value)) { char *old_value = c->value; c->value = spl_utf8_import(old_value, "latin_1"); free(old_value); /* this should never happen */ if (!c->value) c->value = strdup(""); } } } // copied from mod_file.c #define GET_REAL_FILENAME \ char *real_filename; \ if (task->vm->current_dir_name && *filename != '/') { \ int len = 2 + strlen(filename) + \ strlen(task->vm->current_dir_name); \ real_filename = my_alloca(len); \ snprintf(real_filename, len, "%s/%s", \ task->vm->current_dir_name, \ filename); \ } else \ real_filename = filename; /** * This function can be used to save an uploaded file to the local filesystem. * The 1st parameter is the name of the upload form field and the 2nd parameter * the local filename. * * The file will be truncated if it exists already and created if it does not. * * The pseudo-variable 'cgi.param.' contains the name of the file. The * content of the uploaded file can't be accessed directly. * * This function returns the number of bytes written to the harddisk or undef * for an error. */ // builtin cgi_userfile_save(paramname, filename) static struct spl_node *spl_mod_cgi_userfile_save(struct spl_task *task, void UNUSED(*data)) { char *paramname = spl_clib_get_string(task); char *filename = spl_clib_get_string(task); struct cgi_context *ctx = task->vm->cgi_ctx; if ( !ctx ) { spl_report(SPL_REPORT_RUNTIME, task, "CGI: No CGI context found!\n"); return 0; } struct cgi_params_t *p = ctx->params; while (p) { if (!strcmp(p->key, paramname) && p->file_data) { GET_REAL_FILENAME int fd = open(real_filename, O_WRONLY|O_CREAT|O_TRUNC|MY_O_BINARY, 0666); if (!fd) return 0; for (int i=0, rc; i < p->file_size; i+=rc) { rc = write(fd, p->file_data + i, p->file_size - i); if ( rc <= 0 ) { close(fd); return 0; } } close(fd); return SPL_NEW_INT(p->file_size); } p = p->next; } return 0; } /** * This function sends the specified text to the web browser. It also creates * the HTTP header when it is called the first time for an HTTP request. */ // builtin cgi_write(text) struct spl_node *spl_mod_cgi_write(struct spl_task *task, void UNUSED(*data)) { char *text = spl_clib_get_string(task); struct cgi_context *ctx = task->vm->cgi_ctx; if ( !ctx ) { spl_report(SPL_REPORT_RUNTIME, task, "CGI: No CGI context found!\n"); return 0; } if (ctx->content_type) { if (!strncmp(ctx->content_type, "text/", 5)) auto_printf("Content-Type: %s; charset=UTF-8\r\n\r\n", ctx->content_type); else auto_printf("Content-Type: %s\r\n\r\n", ctx->content_type); free(ctx->content_type); ctx->content_type = 0; } auto_printf("%s", text); // fflush(ctx->outfile ? ctx->outfile : stdout); return 0; } /** * This namespace holds all the CGI specific data, like query string parameters * or browser type. */ // namespace cgi /** * A hash with all query string parameters, parameter name as key and * parameter value as value. * * This variable is read-only. */ // var param; /** * A hash with all cookies passed by the browser. Writing to this hash is only * possible as long as no [[cgi_write()]] has been isued. */ // var cookie; /** * A hash with all config variables read from the webspl.conf files. * Example given: * * # In webspl.conf * [ myapp.webspl ] * myapp.dbtype = sqlite * myapp.database = /var/lib/myapp/database.bin * * // In the script * var db = sql_connect(cgi.config.myapp.dbtype, cgi.config.myapp.database); */ // var config; /** * The HTTP content-type of the document to be send back to the browser. This * variable is write-only and must be set before [[cgi_write()]] is called * the first time. It is automatically reset to "text/html" for each new HTTP * response. */ // var content_type = "text/html"; /** * When this variable has a non-zero value, the output created by debug * statements is not sent to the browser. This variable is write-only. It * is automatically reset to '0' for each new HTTP response. */ // var silent_debug = 0; /** * The current session id (with the now running task) * * This variable is read-only. */ // var sid; /** * The current session id (without task) * * This variable is read-only. */ // var sid_vm; /** * The task part from the session id requested by the user. * * This variable is read-only. */ // var sid_task; /** * The session id as requested by the user (vm and task). * * This variable is read-only. */ // var sid_passed; /** * The url requested by the browser (without the query string). This is useful * for building self-references. * * This variable is read-only. */ // var url; /** * The identification string sent by the user agent. * * This variable is read-only. */ // var agent; /** * The IP address of the peer. * * This variable is read-only. */ // var peerip; /** * The mime type of the data sent in a POST request. * * This variable is read-only. */ // var post_type; /** * The data sent in a POST request when it is a text/ mime type. * * This variable is read-only. */ // var post_data; static void handler_cgi_node(struct spl_task *task, struct spl_vm *vm, struct spl_node UNUSED(*node), struct spl_hnode_args *args, void UNUSED(*data)) { const char *key = args->key ? args->key : ""; while (*key == '?') key++; if ( args->action == SPL_HNODE_ACTION_DUMP ) return; struct cgi_context *ctx = vm->cgi_ctx; if ( !ctx ) { if ( args->action != SPL_HNODE_ACTION_PUT ) spl_report(SPL_REPORT_RUNTIME, task, "CGI: No CGI context found!\n"); return; } if ( args->action == SPL_HNODE_ACTION_CREATE ) { char *value = spl_get_string(args->value); if ( !strcmp(key, "content_type") ) { if (ctx->content_type) { free(ctx->content_type); ctx->content_type = strdup(value); } else spl_report(SPL_REPORT_RUNTIME, task, "CGI: Trying to set MIME Type after the HTTP header has been finalized!\n"); return; } if ( !strncmp(key, "cookie.", 6) ) { const char *c = key+7; while (*c == '?') c++; if (ctx->content_type) { auto_printf("Set-Cookie: %s=%s\n", c, value); } else spl_report(SPL_REPORT_RUNTIME, task, "CGI: Trying to set cookie after the HTTP header has been finalized!\n"); return; } if ( !strcmp(key, "silent_debug") ) { ctx->silent_debug = atoi(value); return; } args->value = 0; return; } if ( args->action != SPL_HNODE_ACTION_LOOKUP ) return; if ( !strcmp(key, "sid") ) { char *this_session; my_asprintf(&this_session, "%.*s:%s", (int)strcspn(ctx->session, ":"), ctx->session, task->id); args->value = SPL_NEW_STRING(this_session); } else if ( !strcmp(key, "sid_vm") ) { char *this_session; my_asprintf(&this_session, "%.*s", (int)strcspn(ctx->session, ":"), ctx->session); args->value = SPL_NEW_STRING(this_session); } else if ( !strcmp(key, "sid_task") ) args->value = SPL_NEW_STRING_DUP( ctx->session + (int)strcspn(ctx->session, ":") ); else if ( !strcmp(key, "sid_passed") ) args->value = SPL_NEW_STRING_DUP(ctx->session); else if ( !strcmp(key, "url") && ctx->url ) args->value = SPL_NEW_STRING_DUP(ctx->url); else if ( !strcmp(key, "agent") && ctx->agent ) args->value = SPL_NEW_STRING_DUP(ctx->agent); else if ( !strcmp(key, "peerip") && ctx->peerip ) args->value = SPL_NEW_STRING_DUP(ctx->peerip); else if ( !strcmp(key, "post_type") && ctx->post_type ) args->value = SPL_NEW_STRING_DUP(ctx->post_type); else if ( !strcmp(key, "post_data") && ctx->post_data ) args->value = SPL_NEW_STRING_DUP(ctx->post_data); else if ( !strncmp(key, "param.", 6) ) { char *p = spl_hash_decode(key+6); const char *s = get_param(ctx->params, p); if (s) args->value = SPL_NEW_STRING_DUP(s); free(p); } else if ( !strncmp(key, "cookie.", 7) ) { char *c = spl_hash_decode(key+7); const char *s=get_cookie(ctx->cookies, c); if (s) args->value = SPL_NEW_STRING_DUP(s); free(c); } else if ( !strncmp(key, "config.", 7) ) { char *c = spl_hash_decode(key+7); const char *s=cgi_config_get_str(ctx->config, c); if (s) args->value = SPL_NEW_STRING_DUP(s); free(c); } } struct cgi_context *spl_mod_cgi_get_cgi_ctx(struct http_request *req, struct cgi_config *cfg) { struct cgi_context *ctx = calloc(1, sizeof(struct cgi_context)); ctx->content_type = strdup("text/html"); ctx->config = cfg; if (req) { if (req->url) ctx->url = strdup(req->url); struct http_request_hdr *hdr = req->headers; while (hdr) { if (!strcmp(hdr->name, "user-agent")) ctx->agent = strdup(hdr->value); if (!strcmp(hdr->name, "cookie")) parse_cookie_string(ctx, hdr->value); hdr = hdr->next; } if (req->query) parse_query_string(ctx, req->query, -1, NULL); if (req->data) parse_query_string(ctx, req->data, req->data_len, req->data_type); if (req->peerip) ctx->peerip = strdup(req->peerip); ctx->req = req; } else { char *e; e = getenv("REDIRECT_URL"); if (e) ctx->url = strdup(e); e = getenv("HTTP_USER_AGENT"); if (e) ctx->agent = strdup(e); e = getenv("REMOTE_ADDR"); if (e) ctx->peerip = strdup(e); e = getenv("QUERY_STRING"); if (e) parse_query_string(ctx, e, -1, NULL); e = getenv("HTTP_COOKIE"); if (e) parse_cookie_string(ctx, e); e = getenv("REQUEST_METHOD"); if (e && !strcmp(e, "POST")) { static char *buf = NULL; int buf_size = 1024; int buf_pos = 0; if (buf) free(buf); buf = malloc(1024 + 10); while (1) { if (buf_pos > buf_size-512) { buf_size += 1024; buf = realloc(buf, buf_size + 10); } int rc = read(0, buf+buf_pos, buf_size-buf_pos); if (rc <= 0) break; buf_pos += rc; } buf[buf_pos] = 0; e = getenv("CONTENT_TYPE"); parse_query_string(ctx, buf, buf_pos, e); } } ctx->session = get_param(ctx->params, "sid"); if(!ctx->session) { const char *sessioncookie = cgi_config_get_str(cfg, "spl.sessioncookie"); if (sessioncookie) ctx->session = get_cookie(ctx->cookies, sessioncookie); } for (int i=0; ctx->session && ctx->session[i]; i++) { if ( ctx->session[i] >= '0' && ctx->session[i] <= '9' ) continue; if ( ctx->session[i] >= 'A' && ctx->session[i] <= 'Z' ) continue; if ( ctx->session[i] >= 'a' && ctx->session[i] <= 'z' ) continue; if ( i && ctx->session[i] == ':' ) break; ctx->session = 0; } ctx->session = strdup(ctx->session ? ctx->session : ""); ctx->report_count = 0; return ctx; } void spl_mod_cgi_free_cgi_ctx(struct cgi_context *ctx) { while (ctx->params) { struct cgi_params_t *n = ctx->params->next; free(ctx->params->key); free(ctx->params->value); free(ctx->params); ctx->params = n; } while (ctx->cookies) { struct cgi_cookie_t *n = ctx->cookies->next; free(ctx->cookies->key); free(ctx->cookies->value); free(ctx->cookies); ctx->cookies = n; } if (ctx->content_type) free(ctx->content_type); if (ctx->session) free(ctx->session); if (ctx->url) free(ctx->url); if (ctx->agent) free(ctx->agent); if (ctx->peerip) free(ctx->peerip); if (ctx->post_type) free(ctx->post_type); if (ctx->post_data) free(ctx->post_data); free(ctx); } void spl_mod_cgi_reportfunc(int type, void *desc, const char *fmt, ...) { va_list ap; va_start(ap, fmt); char *msg = spl_report_string(type, desc, fmt, ap); va_end(ap); struct cgi_context *ctx = 0; if ( desc ) switch (type & 0x000f) { case SPL_REPORT_HOST: case SPL_REPORT_RESTORE: { struct spl_vm *vm = desc; ctx = vm->cgi_ctx; break; } case SPL_REPORT_ASSEMBLER: case SPL_REPORT_COMPILER: case SPL_REPORT_LEXER: { struct spl_asm *as = desc; if (as->vm) ctx = as->vm->cgi_ctx; break; } case SPL_REPORT_RUNTIME: { struct spl_task *task = desc; ctx = task->vm->cgi_ctx; break; } } if ( ctx && ctx->outfile ) { printf("### "); for (int i=0; msg[i]; i++) if (msg[i] == '\n' && msg[i+1]) printf("\n### "); else printf("%c", msg[i]); fflush(stdout); } if (ctx && ctx->silent_debug && (type & SPL_REPORT_DEBUG)) goto skip_debug_browser; if (!ctx || ctx->content_type) { auto_printf("Content-Type: text/html; charset=UTF-8\r\n\r\n"); if (ctx) { free(ctx->content_type); ctx->content_type = 0; } } if (ctx) ctx->report_count++; auto_printf("
", ctx ? ctx->report_count : 0);
	for (int i=0; msg[i]; i++)
		switch (msg[i])
		{
		case '<':
			auto_printf("<");
			break;
		case '>':
			auto_printf(">");
			break;
		case '&':
			auto_printf("&");
			break;
		default:
			auto_printf("%c", msg[i]);
		}
	auto_printf("
\n"); if (ctx) { if ( ctx->report_count < 4 ) { auto_printf("\n"); } if ( ctx->report_count == 4 ) { auto_printf("\n"); } } fflush(ctx && ctx->outfile ? ctx->outfile : stdout); skip_debug_browser: free(msg); } void SPL_ABI(spl_mod_cgi_init)(struct spl_vm *vm, struct spl_module *mod, int restore) { if (!vm->cgi_ctx) vm->cgi_ctx = spl_mod_cgi_get_cgi_ctx(0, 0); spl_clib_reg(vm, "cgi_write", spl_mod_cgi_write, 0); spl_clib_reg(vm, "cgi_userfile_save", spl_mod_cgi_userfile_save, 0); spl_hnode_reg(vm, "cgi_node", handler_cgi_node, 0); if (!restore) spl_hnode(vm, vm->root, "cgi", "cgi_node", mod); } void SPL_ABI(spl_mod_cgi_done)(struct spl_vm *vm, struct spl_module UNUSED(*mod)) { if (vm->cgi_ctx) { spl_mod_cgi_free_cgi_ctx(vm->cgi_ctx); vm->cgi_ctx = 0; } return; }