X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~mdw/git/disorder/blobdiff_plain/5e34540b562f91c7b383a307c32e1a159266dd11..c617bac5ba2c520255597c36a9e25f8ae704cfd0:/server/cgi.c diff --git a/server/cgi.c b/server/cgi.c index 5e475ed..ef693da 100644 --- a/server/cgi.c +++ b/server/cgi.c @@ -1,6 +1,6 @@ /* * This file is part of DisOrder. - * Copyright (C) 2004, 2005, 2006 Richard Kettlewell + * Copyright (C) 2004-2008 Richard Kettlewell * * 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 @@ -53,6 +53,7 @@ #include "printf.h" #include "mime.h" #include "unicode.h" +#include "hash.h" struct kvp *cgi_args; @@ -64,6 +65,41 @@ struct column { char **columns; }; +/* macros */ +struct cgi_macro { + int nargs; + char **args; + const char *value; +}; + +static hash *cgi_macros; + +/** @brief Parse of a template */ +struct cgi_element { + /** @brief Next element */ + struct cgi_element *next; + + /** @brief Element type */ + int type; +#define ELEMENT_TEXT 0 +#define ELEMENT_EXPANSION 1 + + /** @brief Line number at start of element */ + int line; + + /** @brief Plain text */ + char *text; + + /** @brief Expansion name */ + char *name; + + /** @brief Argument count */ + int nargs; + + /** @brief Argument values (NOT recursively expanded) */ + char **args; +}; + #define RELIST(x) struct re *x, **x##_tail = &x static int have_read_options; @@ -71,6 +107,12 @@ static struct kvp *labels; static struct column *columns; static void include_options(const char *name); +static void cgi_expand_parsed(const char *name, + struct cgi_element *head, + const struct cgi_expansion *expansions, + size_t nexpansions, + cgi_sink *output, + void *u); static void cgi_parse_get(void) { const char *q; @@ -152,13 +194,14 @@ static void cgi_parse_multipart(const char *boundary) { } static void cgi_parse_post(void) { - const char *ct; - char *q, *type, *pname, *pvalue; + const char *ct, *boundary; + char *q, *type; size_t n; + struct kvp *k; if(!(ct = getenv("CONTENT_TYPE"))) ct = "application/x-www-form-urlencoded"; - if(mime_content_type(ct, &type, &pname, &pvalue)) + if(mime_content_type(ct, &type, &k)) fatal(0, "invalid content type '%s'", ct); if(!strcmp(type, "application/x-www-form-urlencoded")) { cgi_input(&q, &n); @@ -166,10 +209,9 @@ static void cgi_parse_post(void) { return; } if(!strcmp(type, "multipart/form-data")) { - if(!pname || strcmp(pname, "boundary")) - fatal(0, "expected a boundary parameter, found %s", - pname ? pname : "nothing"); - cgi_parse_multipart(pvalue); + if(!(boundary = kvp_get(k, "boundary"))) + fatal(0, "no boundary parameter found"); + cgi_parse_multipart(boundary); return; } fatal(0, "unrecognized content type '%s'", type); @@ -360,35 +402,44 @@ void cgi_expand(const char *template, cgi_expand_string(template, b, expansions, nexpansions, output, u); } -void cgi_expand_string(const char *name, - const char *template, - const struct cgi_expansion *expansions, - size_t nexpansions, - cgi_sink *output, - void *u) { - int braces, n, m, line = 1, sline; - char *argname; +/** @brief Return a linked list of the parse of @p template */ +static struct cgi_element *cgi_parse_string(const char *name, + const char *template) { + int braces, line = 1, sline; const char *p; struct vector v; struct dynstr d; - cgi_sink parameter_output; - + struct cgi_element *head = 0, **tailp = &head, *e; + while(*template) { if(*template != '@') { - p = template; - while(*p && *p != '@') { - if(*p == '\n') ++line; - ++p; + sline = line; + dynstr_init(&d); + /* Gather up text without any expansions in. */ + while(*template && *template != '@') { + if(*template == '\n') + ++line; + dynstr_append(&d, *template++); } - output->sink->write(output->sink, template, p - template); - template = p; + dynstr_terminate(&d); + e = xmalloc(sizeof *e); + e->next = 0; + e->line = sline; + e->type = ELEMENT_TEXT; + e->text = d.vec; + *tailp = e; + tailp = &e->next; continue; } vector_init(&v); braces = 0; + p = template; ++template; sline = line; while(*template != '@') { + /* Skip whitespace */ + while(isspace((unsigned char)*template)) + ++template; dynstr_init(&d); if(*template == '{') { /* bracketed arg */ @@ -401,16 +452,21 @@ void cgi_expand_string(const char *name, } dynstr_append(&d, *template++); } - if(!*template) fatal(0, "%s:%d: unterminated expansion", name, sline); + if(!*template) fatal(0, "%s:%d: unterminated expansion '%.*s'", + name, sline, (int)(template - p), p); ++template; - /* skip whitespace after closing bracket */ - while(isspace((unsigned char)*template)) - ++template; + if(isspace((unsigned char)*template)) { + /* We have @{...} */ + for(p = template; isspace((unsigned char)*p); ++p) + ; + /* Now we are looking at . If it's "{" then that + * must be the next argument. Otherwise we infer that this + * is really the end of the expansion. */ + if(*p != '{') + goto finished_expansion; + } } else { /* unbracketed arg */ - /* leading whitespace is not significant in unquoted args */ - while(isspace((unsigned char)*template)) - ++template; while(*template && *template != '@' && *template != '{' && *template != ':') { if(*template == '\n') ++line; @@ -418,7 +474,8 @@ void cgi_expand_string(const char *name, } if(*template == ':') ++template; - if(!*template) fatal(0, "%s:%d: unterminated expansion", name, sline); + if(!*template) fatal(0, "%s:%d: unterminated expansion '%.*s'", + name, sline, (int)(template - p), p); /* trailing whitespace is not significant in unquoted args */ while(d.nvec && (isspace((unsigned char)d.vec[d.nvec - 1]))) --d.nvec; @@ -427,37 +484,154 @@ void cgi_expand_string(const char *name, vector_append(&v, d.vec); } ++template; + finished_expansion: vector_terminate(&v); /* @@ terminates this file */ if(v.nvec == 0) break; - if((n = table_find(expansions, - offsetof(struct cgi_expansion, name), - sizeof (struct cgi_expansion), - nexpansions, - v.vec[0])) < 0) - fatal(0, "%s:%d: unknown expansion '%s'", name, line, v.vec[0]); - if(v.nvec - 1 < expansions[n].minargs) - fatal(0, "%s:%d: insufficient arguments to @%s@ (min %d, got %d)", - name, line, v.vec[0], expansions[n].minargs, v.nvec - 1); - if(v.nvec - 1 > expansions[n].maxargs) - fatal(0, "%s:%d: too many arguments to @%s@ (max %d, got %d)", - name, line, v.vec[0], expansions[n].maxargs, v.nvec - 1); - /* for ordinary expansions, recursively expand the arguments */ - if(!(expansions[n].flags & EXP_MAGIC)) { - for(m = 1; m < v.nvec; ++m) { - dynstr_init(&d); - byte_xasprintf(&argname, "<%s:%d arg #%d>", name, sline, m); - parameter_output.quote = 0; - parameter_output.sink = sink_dynstr(&d); - cgi_expand_string(argname, v.vec[m], - expansions, nexpansions, - ¶meter_output, u); - dynstr_terminate(&d); - v.vec[m] = d.vec; + e = xmalloc(sizeof *e); + e->next = 0; + e->line = sline; + e->type = ELEMENT_EXPANSION; + e->name = v.vec[0]; + e->nargs = v.nvec - 1; + e->args = &v.vec[1]; + *tailp = e; + tailp = &e->next; + } + return head; +} + +void cgi_expand_string(const char *name, + const char *template, + const struct cgi_expansion *expansions, + size_t nexpansions, + cgi_sink *output, + void *u) { + cgi_expand_parsed(name, cgi_parse_string(name, template), + expansions, nexpansions, output, u); +} + +/** @brief Expand a list of arguments in place */ +static void cgi_expand_all_args(const char *name, + int line, + const struct cgi_expansion *expansions, + size_t nexpansions, + void *u, + char **args, + int nargs) { + int n; + struct dynstr d; + char *argname; + cgi_sink parameter_output; + + for(n = 0; n < nargs; ++n) { + dynstr_init(&d); + byte_xasprintf(&argname, "<%s:%d arg #%d>", name, line,n); + parameter_output.quote = 0; + parameter_output.sink = sink_dynstr(&d); + cgi_expand_string(argname, args[n], + expansions, nexpansions, + ¶meter_output, u); + dynstr_terminate(&d); + args[n] = d.vec; + } +} + + +/** @brief Substitute macro arguments in place */ +static void cgi_substitute_args(const char *name, + struct cgi_element *head, + const struct cgi_macro *macro, + char **values) { + struct cgi_element *e; + int n; + + for(e = head; e; e = e->next) { + if(e->type != ELEMENT_EXPANSION) + continue; + /* See if this is an argument name */ + for(n = 0; n < macro->nargs; ++n) + if(!strcmp(e->name, macro->args[n])) + break; + if(n < macro->nargs) { + /* It is! */ + if(e->nargs != 0) + fatal(0, "%s:%d: macro argument (%s) cannot take parameters", + name, e->line, e->name); + /* Replace it with the argument text */ + e->type = ELEMENT_TEXT; + e->text = values[n]; + continue; + } + /* It's not a macro argument. We must recurse into its arguments to + * substitute the macro arguments. */ + /* TODO */ + /* In order to do this we must parse it and our callers must expect the + * parsed form. We're not ready for this yet... */ + } +} + +static void cgi_expand_parsed(const char *name, + struct cgi_element *head, + const struct cgi_expansion *expansions, + size_t nexpansions, + cgi_sink *output, + void *u) { + int n; + const struct cgi_macro *macro; + struct cgi_element *e, *macro_head; + + for(e = head; e; e = e->next) { + switch(e->type) { + case ELEMENT_TEXT: + output->sink->write(output->sink, e->text, strlen(e->text)); + break; + case ELEMENT_EXPANSION: + if((n = table_find(expansions, + offsetof(struct cgi_expansion, name), + sizeof (struct cgi_expansion), + nexpansions, + e->name)) >= 0) { + /* We found a built-in */ + if(e->nargs < expansions[n].minargs) + fatal(0, "%s:%d: insufficient arguments to @%s@ (min %d, got %d)", + name, e->line, e->name, expansions[n].minargs, e->nargs); + if(e->nargs > expansions[n].maxargs) + fatal(0, "%s:%d: too many arguments to @%s@ (max %d, got %d)", + name, e->line, e->name, expansions[n].maxargs, e->nargs); + /* for ordinary expansions, recursively expand the arguments */ + if(!(expansions[n].flags & EXP_MAGIC)) + cgi_expand_all_args(name, e->line, expansions, nexpansions, u, + e->args, e->nargs); + expansions[n].handler(e->nargs, e->args, output, u); + } else if(cgi_macros && (macro = hash_find(cgi_macros, e->name))) { + /* We found a macro */ + if(e->nargs != macro->nargs) + fatal(0, "%s:%d: wrong number of arguments to @%s@ (need %d, got %d)", + name, e->line, e->name, macro->nargs, e->nargs); + /* Expand arguments */ + cgi_expand_all_args(name, e->line, expansions, nexpansions, u, + e->args, e->nargs); + /* Parse the macro value. Doing this every time isn't very efficient, + * but NB that we mess with the result of the parse, so for the time + * being we do need to do it. */ + macro_head = cgi_parse_string(e->name, macro->value); + /* Substitute in argument values */ + cgi_substitute_args(name, macro_head, macro, e->args); + /* Expand the result */ + cgi_expand_parsed(e->name, + macro_head, + expansions, + nexpansions, + output, + u); + } else { + /* Totally undefined */ + fatal(0, "%s:%d: unknown expansion '%s'", name, e->line, e->name); } + break; } - expansions[n].handler(v.nvec - 1, v.vec + 1, output, u); } } @@ -603,6 +777,11 @@ const char *cgi_label(const char *key) { return label; } +int cgi_label_exists(const char *key) { + read_options(); + return kvp_get(labels, key) ? 1 : 0; +} + char **cgi_columns(const char *name, int *ncolumns) { struct column *c; @@ -620,6 +799,20 @@ char **cgi_columns(const char *name, int *ncolumns) { } } +void cgi_define(const char *name, + int nargs, + char **args, + const char *value) { + struct cgi_macro m; + + if(!cgi_macros) + cgi_macros = hash_new(sizeof(struct cgi_macro)); + m.nargs = nargs; + m.args = args; + m.value = value; + hash_add(cgi_macros, name, &m, HASH_INSERT_OR_REPLACE); +} + /* Local Variables: c-basic-offset:2