chiark / gitweb /
Synchronize with disorder.dev
authorRichard Kettlewell <rjk@greenend.org.uk>
Sat, 3 May 2008 19:15:50 +0000 (20:15 +0100)
committerRichard Kettlewell <rjk@greenend.org.uk>
Sat, 3 May 2008 19:15:50 +0000 (20:15 +0100)
doc/disorder_config.5.in
server/cgi.c
server/cgi.h
server/dcgi.c

index e714a22549b0b5b76f700150cb9a76756f791bac..c9b20618a381435dce735b0c309b0e836024fcf7 100644 (file)
@@ -907,14 +907,20 @@ character reference, e.g. \fB&#253;\fR.
 Use \fB&#64;\fR to insert a literal \fB@\fR without falling foul of
 the expansion syntax.
 .SS "Expansion Syntax"
-Expansions are surrounded by at ("@") symbols take the form of a keyword
-followed by zero or more arguments.
-Arguments may either be quoted by curly brackets ("{" and "}") or separated
+An expansion starts with an at ("@") symbol and takes the form of an expansion
+name followed by zero or more arguments.
+.PP
+Arguments can be quoted by curly brackets ("{" and "}") or separated
 by colons (":").
 Both kinds may be mixed in a single expansion, though doing so seems
 likely to cause confusion.
 The descriptions below contain suggested forms for each expansion.
 .PP
+An expansion is terminated by another "@" symbol, but this is optional in the
+specific case that the last argument is quoted by curly brackets and it is
+followed by whitespace and then some character that is not "{" (since that
+could be interpreted as a further argument).
+.PP
 Leading and trailing whitespace in unquoted arguments is ignored, as is
 whitespace (including newlines) following a close bracket ("}").
 .PP
index 2017d1b5fdf4f31b48f1d00d440dc5da1aa5e93a..ef693dae4f20a6a900bb861a271a4cd9aa1e6bf2 100644 (file)
@@ -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;
@@ -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 @{...}<WHITESPACE><SOMETHING> */
+         for(p = template; isspace((unsigned char)*p); ++p)
+           ;
+         /* Now we are looking at <SOMETHING>.  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,
-                         &parameter_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,
+                     &parameter_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);
   }
 }
 
@@ -625,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
index 2381c033bf609b12962b135b93d99d94f2434211..8a76d313221686eb6f63a9c58554e5e22a958606 100644 (file)
@@ -65,6 +65,11 @@ struct cgi_expansion {
   void (*handler)(int nargs, char **args, cgi_sink *output, void *u);
 };
 
+void cgi_define(const char *name,
+               int nargs,
+               char **args,
+               const char *value);
+
 void cgi_expand(const char *name,
                const struct cgi_expansion *expansions,
                size_t nexpansions,
index 33630faa85ea2e037bc3fb73ae9d29c835c2aa8b..16a5a6cb6c9698994f6a5d97995b9e679fb1fadd 100644 (file)
@@ -1760,6 +1760,17 @@ static void exp_image(int attribute((unused)) nargs,
     cgi_output(output, "/disorder/%s", imagestem);
 }
 
+static void exp_define(int attribute((unused)) nargs,
+                      char **args,
+                      cgi_sink attribute((unused)) *output,
+                      void attribute((unused)) *u) {
+  const char *n = args[0], *a = args[1], *v = args[2];
+  int nas;
+  char **as = split(a, &nas, 0, 0, 0);
+
+  cgi_define(n, nas, as, v);
+}
+
 static const struct cgi_expansion expansions[] = {
   { "#", 0, INT_MAX, EXP_MAGIC, exp_comment },
   { "action", 0, 0, 0, exp_action },
@@ -1767,6 +1778,7 @@ static const struct cgi_expansion expansions[] = {
   { "arg", 1, 1, 0, exp_arg },
   { "basename", 0, 1, 0, exp_basename },
   { "choose", 2, 2, EXP_MAGIC, exp_choose },
+  { "define", 3, 3, EXP_MAGIC, exp_define },
   { "dirname", 0, 1, 0, exp_dirname },
   { "enabled", 0, 0, 0, exp_enabled },
   { "eq", 2, 2, 0, exp_eq },