chiark / gitweb /
Further rearrangement to support macro expansion.
[disorder] / server / cgi.c
1 /*
2  * This file is part of DisOrder.
3  * Copyright (C) 2004-2008 Richard Kettlewell
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful, but
11  * WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
18  * USA
19  */
20
21 #include <config.h>
22 #include "types.h"
23
24 #include <string.h>
25 #include <stdio.h>
26 #include <unistd.h>
27 #include <stdlib.h>
28 #include <errno.h>
29 #include <sys/stat.h>
30 #include <stddef.h>
31 #include <fcntl.h>
32 #include <unistd.h>
33 #include <pcre.h>
34 #include <limits.h>
35 #include <fnmatch.h>
36 #include <ctype.h>
37
38 #include "mem.h"
39 #include "log.h"
40 #include "hex.h"
41 #include "charset.h"
42 #include "configuration.h"
43 #include "table.h"
44 #include "syscalls.h"
45 #include "kvp.h"
46 #include "vector.h"
47 #include "split.h"
48 #include "inputline.h"
49 #include "regsub.h"
50 #include "defs.h"
51 #include "sink.h"
52 #include "cgi.h"
53 #include "printf.h"
54 #include "mime.h"
55 #include "unicode.h"
56 #include "hash.h"
57
58 struct kvp *cgi_args;
59
60 /* options */
61 struct column {
62   struct column *next;
63   char *name;
64   int ncolumns;
65   char **columns;
66 };
67
68 /* macros */
69 struct cgi_macro {
70   int nargs;
71   char **args;
72   const char *value;
73 };
74
75 static hash *cgi_macros;
76
77 /** @brief Parse of a template */
78 struct cgi_element {
79   /** @brief Next element */
80   struct cgi_element *next;
81
82   /** @brief Element type */
83   int type;
84 #define ELEMENT_TEXT 0
85 #define ELEMENT_EXPANSION 1
86
87   /** @brief Line number at start of element */
88   int line;
89   
90   /** @brief Plain text */
91   char *text;
92
93   /** @brief Expansion name */
94   char *name;
95
96   /** @brief Argument count */
97   int nargs;
98
99   /** @brief Argument values (NOT recursively expanded) */
100   char **args;
101 };
102
103 #define RELIST(x) struct re *x, **x##_tail = &x
104
105 static int have_read_options;
106 static struct kvp *labels;
107 static struct column *columns;
108
109 static void include_options(const char *name);
110 static void cgi_expand_parsed(const char *name,
111                               struct cgi_element *head,
112                               const struct cgi_expansion *expansions,
113                               size_t nexpansions,
114                               cgi_sink *output,
115                               void *u);
116
117 static void cgi_parse_get(void) {
118   const char *q;
119
120   if(!(q = getenv("QUERY_STRING"))) fatal(0, "QUERY_STRING not set");
121   cgi_args = kvp_urldecode(q, strlen(q));
122 }
123
124 static void cgi_input(char **ptrp, size_t *np) {
125   const char *cl;
126   char *q;
127   size_t n, m = 0;
128   int r;
129
130   if(!(cl = getenv("CONTENT_LENGTH"))) fatal(0, "CONTENT_LENGTH not set");
131   n = atol(cl);
132   q = xmalloc_noptr(n + 1);
133   while(m < n) {
134     r = read(0, q + m, n - m);
135     if(r > 0)
136       m += r;
137     else if(r == 0)
138       fatal(0, "unexpected end of file reading request body");
139     else switch(errno) {
140     case EINTR: break;
141     default: fatal(errno, "error reading request body");
142     }
143   }
144   if(memchr(q, 0, n)) fatal(0, "null character in request body");
145   q[n + 1] = 0;
146   *ptrp = q;
147   if(np) *np = n;
148 }
149
150 static int cgi_field_callback(const char *name, const char *value,
151                               void *u) {
152   char *disposition, *pname, *pvalue;
153   char **namep = u;
154
155   if(!strcmp(name, "content-disposition")) {
156     if(mime_rfc2388_content_disposition(value,
157                                         &disposition,
158                                         &pname,
159                                         &pvalue))
160       fatal(0, "error parsing Content-Disposition field");
161     if(!strcmp(disposition, "form-data")
162        && pname
163        && !strcmp(pname, "name")) {
164       if(*namep)
165         fatal(0, "duplicate Content-Disposition field");
166       *namep = pvalue;
167     }
168   }
169   return 0;
170 }
171
172 static int cgi_part_callback(const char *s,
173                              void attribute((unused)) *u) {
174   char *name = 0;
175   struct kvp *k;
176   
177   if(!(s = mime_parse(s, cgi_field_callback, &name)))
178     fatal(0, "error parsing part header");
179   if(!name) fatal(0, "no name found");
180   k = xmalloc(sizeof *k);
181   k->next = cgi_args;
182   k->name = name;
183   k->value = s;
184   cgi_args = k;
185   return 0;
186 }
187
188 static void cgi_parse_multipart(const char *boundary) {
189   char *q;
190   
191   cgi_input(&q, 0);
192   if(mime_multipart(q, cgi_part_callback, boundary, 0))
193     fatal(0, "invalid multipart object");
194 }
195
196 static void cgi_parse_post(void) {
197   const char *ct, *boundary;
198   char *q, *type;
199   size_t n;
200   struct kvp *k;
201
202   if(!(ct = getenv("CONTENT_TYPE")))
203     ct = "application/x-www-form-urlencoded";
204   if(mime_content_type(ct, &type, &k))
205     fatal(0, "invalid content type '%s'", ct);
206   if(!strcmp(type, "application/x-www-form-urlencoded")) {
207     cgi_input(&q, &n);
208     cgi_args = kvp_urldecode(q, n);
209     return;
210   }
211   if(!strcmp(type, "multipart/form-data")) {
212     if(!(boundary = kvp_get(k, "boundary")))
213       fatal(0, "no boundary parameter found");
214     cgi_parse_multipart(boundary);
215     return;
216   }
217   fatal(0, "unrecognized content type '%s'", type);
218 }
219
220 void cgi_parse(void) {
221   const char *p;
222   struct kvp *k;
223
224   if(!(p = getenv("REQUEST_METHOD"))) fatal(0, "REQUEST_METHOD not set");
225   if(!strcmp(p, "GET"))
226     cgi_parse_get();
227   else if(!strcmp(p, "POST"))
228     cgi_parse_post();
229   else
230     fatal(0, "unknown request method %s", p);
231   for(k = cgi_args; k; k = k->next)
232     if(!utf8_valid(k->name, strlen(k->name))
233        || !utf8_valid(k->value, strlen(k->value)))
234       fatal(0, "invalid UTF-8 sequence in cgi argument");
235 }
236
237 const char *cgi_get(const char *name) {
238   return kvp_get(cgi_args, name);
239 }
240
241 void cgi_output(cgi_sink *output, const char *fmt, ...) {
242   va_list ap;
243   int n;
244   char *r;
245
246   va_start(ap, fmt);
247   n = byte_vasprintf(&r, fmt, ap);
248   if(n < 0)
249     fatal(errno, "error calling byte_vasprintf");
250   if(output->quote)
251     r = cgi_sgmlquote(r, 0);
252   output->sink->write(output->sink, r, strlen(r));
253   va_end(ap);
254 }
255
256 void cgi_header(struct sink *output, const char *name, const char *value) {
257   sink_printf(output, "%s: %s\r\n", name, value);
258 }
259
260 void cgi_body(struct sink *output) {
261   sink_printf(output, "\r\n");
262 }
263
264 char *cgi_sgmlquote(const char *s, int raw) {
265   uint32_t *ucs, *p, c;
266   char *b, *bp;
267   int n;
268
269   if(!raw) {
270     if(!(ucs = utf8_to_utf32(s, strlen(s), 0))) exit(EXIT_FAILURE);
271   } else {
272     ucs = xmalloc_noptr((strlen(s) + 1) * sizeof(uint32_t));
273     for(n = 0; s[n]; ++n)
274       ucs[n] = (unsigned char)s[n];
275     ucs[n] = 0;
276   }
277
278   n = 1;
279   /* estimate the length we'll need */
280   for(p = ucs; (c = *p); ++p) {
281     switch(c) {
282     default:
283       if(c > 127 || c < 32) {
284       case '"':
285       case '&':
286       case '<':
287       case '>':
288         n += 12;
289         break;
290       } else
291         n++;
292     }
293   }
294   /* format the string */
295   b = bp = xmalloc_noptr(n);
296   for(p = ucs; (c = *p); ++p) {
297     switch(c) {
298     default:
299       if(*p > 127 || *p < 32) {
300       case '"':
301       case '&':
302       case '<':
303       case '>':
304         bp += sprintf(bp, "&#%lu;", (unsigned long)c);
305         break;
306       } else
307         *bp++ = c;
308     }
309   }
310   *bp = 0;
311   return b;
312 }
313
314 void cgi_attr(struct sink *output, const char *name, const char *value) {
315   if(!value[strspn(value, "abcdefghijklmnopqrstuvwxyz"
316                    "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
317                    "0123456789")])
318     sink_printf(output, "%s=%s", name, value);
319   else
320     sink_printf(output, "%s=\"%s\"", name, cgi_sgmlquote(value, 0));
321 }
322
323 void cgi_opentag(struct sink *output, const char *name, ...) {
324   va_list ap;
325   const char *n, *v;
326    
327   sink_printf(output, "<%s", name);
328   va_start(ap, name);
329   while((n = va_arg(ap, const char *))) {
330     sink_printf(output, " ");
331     v = va_arg(ap, const char *);
332     if(v)
333       cgi_attr(output, n, v);
334     else
335       sink_printf(output, n);
336   }
337   sink_printf(output, ">");
338 }
339
340 void cgi_closetag(struct sink *output, const char *name) {
341   sink_printf(output, "</%s>", name);
342 }
343
344 static int template_open(const char *name,
345                          const char *ext,
346                          const char **filenamep) {
347   const char *dirs[2];
348   int fd = -1, n;
349   char *fullpath;
350
351   dirs[0] = pkgconfdir;
352   dirs[1] = pkgdatadir;
353   if(name[0] == '/') {
354     if((fd = open(name, O_RDONLY)) < 0) fatal(0, "cannot open %s", name);
355     *filenamep = name;
356   } else {
357     for(n = 0; n < config->templates.n + (int)(sizeof dirs / sizeof *dirs); ++n) {
358       byte_xasprintf(&fullpath, "%s/%s%s",
359                      n < config->templates.n ? config->templates.s[n]
360                                              : dirs[n - config->templates.n],
361                      name, ext);
362       if((fd = open(fullpath, O_RDONLY)) >= 0) break;
363     }
364     if(fd < 0) error(0, "cannot find %s%s in template path", name, ext);
365     *filenamep = fullpath;
366   }
367   return fd;
368 }
369
370 static int valid_template_name(const char *name) {
371   if(strchr(name, '/') || name[0] == '.')
372     return 0;
373   return 1;
374 }
375
376 void cgi_expand(const char *template,
377                 const struct cgi_expansion *expansions,
378                 size_t nexpansions,
379                 cgi_sink *output,
380                 void *u) {
381   int fd = -1;
382   int n;
383   off_t m;
384   char *b;
385   struct stat sb;
386
387   if(!valid_template_name(template))
388     fatal(0, "invalid template name '%s'", template);
389   if((fd = template_open(template, ".html", &template)) < 0)
390     exitfn(EXIT_FAILURE);
391   if(fstat(fd, &sb) < 0) fatal(errno, "cannot stat %s", template);
392   m = 0;
393   b = xmalloc_noptr(sb.st_size + 1);
394   while(m < sb.st_size) {
395     n = read(fd, b + m, sb.st_size - m);
396     if(n > 0) m += n;
397     else if(n == 0) fatal(0, "unexpected EOF reading %s", template);
398     else if(errno != EINTR) fatal(errno, "error reading %s", template);
399   }
400   b[sb.st_size] = 0;
401   xclose(fd);
402   cgi_expand_string(template, b, expansions, nexpansions, output, u);
403 }
404
405 /** @brief Return a linked list of the parse of @p template */
406 static struct cgi_element *cgi_parse_string(const char *name,
407                                             const char *template) {
408   int braces, line = 1, sline;
409   const char *p;
410   struct vector v;
411   struct dynstr d;
412   struct cgi_element *head = 0, **tailp = &head, *e;
413
414   while(*template) {
415     if(*template != '@') {
416       sline = line;
417       dynstr_init(&d);
418       /* Gather up text without any expansions in. */
419       while(*template && *template != '@') {
420         if(*template == '\n')
421           ++line;
422         dynstr_append(&d, *template++);
423       }
424       dynstr_terminate(&d);
425       e = xmalloc(sizeof *e);
426       e->next = 0;
427       e->line = sline;
428       e->type = ELEMENT_TEXT;
429       e->text = d.vec;
430       *tailp = e;
431       tailp = &e->next;
432       continue;
433     }
434     vector_init(&v);
435     braces = 0;
436     p = template;
437     ++template;
438     sline = line;
439     while(*template != '@') {
440       /* Skip whitespace */
441       while(isspace((unsigned char)*template))
442         ++template;
443       dynstr_init(&d);
444       if(*template == '{') {
445         /* bracketed arg */
446         ++template;
447         while(*template && (*template != '}' || braces > 0)) {
448           switch(*template) {
449           case '{': ++braces; break;
450           case '}': --braces; break;
451           case '\n': ++line; break;
452           }
453           dynstr_append(&d, *template++);
454         }
455         if(!*template) fatal(0, "%s:%d: unterminated expansion '%.*s'",
456                              name, sline, (int)(template - p), p);
457         ++template;
458         if(isspace((unsigned char)*template)) {
459           /* We have @{...}<WHITESPACE><SOMETHING> */
460           for(p = template; isspace((unsigned char)*p); ++p)
461             ;
462           /* Now we are looking at <SOMETHING>.  If it's "{" then that
463            * must be the next argument.  Otherwise we infer that this
464            * is really the end of the expansion. */
465           if(*p != '{')
466             goto finished_expansion;
467         }
468       } else {
469         /* unbracketed arg */
470         while(*template
471               && *template != '@' && *template != '{' && *template != ':') {
472           if(*template == '\n') ++line;
473           dynstr_append(&d, *template++);
474         }
475         if(*template == ':')
476           ++template;
477         if(!*template) fatal(0, "%s:%d: unterminated expansion '%.*s'",
478                              name, sline, (int)(template - p), p);
479         /* trailing whitespace is not significant in unquoted args */
480         while(d.nvec && (isspace((unsigned char)d.vec[d.nvec - 1])))
481           --d.nvec;
482       }
483       dynstr_terminate(&d);
484       vector_append(&v, d.vec);
485     }
486     ++template;
487     finished_expansion:
488     vector_terminate(&v);
489     /* @@ terminates this file */
490     if(v.nvec == 0)
491       break;
492     e = xmalloc(sizeof *e);
493     e->next = 0;
494     e->line = sline;
495     e->type = ELEMENT_EXPANSION;
496     e->name = v.vec[0];
497     e->nargs = v.nvec - 1;
498     e->args = &v.vec[1];
499     *tailp = e;
500     tailp = &e->next;
501   }
502   return head;
503 }
504
505 void cgi_expand_string(const char *name,
506                        const char *template,
507                        const struct cgi_expansion *expansions,
508                        size_t nexpansions,
509                        cgi_sink *output,
510                        void *u) {
511   cgi_expand_parsed(name, cgi_parse_string(name, template),
512                     expansions, nexpansions, output, u);
513 }
514
515 /** @brief Expand a list of arguments in place */
516 static void cgi_expand_all_args(const char *name,
517                                 int line,
518                                 const struct cgi_expansion *expansions,
519                                 size_t nexpansions,
520                                 void *u,
521                                 char **args,
522                                 int nargs) {
523   int n;
524   struct dynstr d;
525   char *argname;
526   cgi_sink parameter_output;
527
528   for(n = 0; n < nargs; ++n) {
529     dynstr_init(&d);
530     byte_xasprintf(&argname, "<%s:%d arg #%d>", name, line,n);
531     parameter_output.quote = 0;
532     parameter_output.sink = sink_dynstr(&d);
533     cgi_expand_string(argname, args[n],
534                       expansions, nexpansions,
535                       &parameter_output, u);
536     dynstr_terminate(&d);
537     args[n] = d.vec;
538   }
539 }
540                          
541
542 /** @brief Substitute macro arguments in place */
543 static void cgi_substitute_args(const char *name,
544                                 struct cgi_element *head,
545                                 const struct cgi_macro *macro,
546                                 char **values) {
547   struct cgi_element *e;
548   int n;
549
550   for(e = head; e; e = e->next) {
551     if(e->type != ELEMENT_EXPANSION)
552       continue;
553     /* See if this is an argument name */
554     for(n = 0; n < macro->nargs; ++n)
555       if(!strcmp(e->name, macro->args[n]))
556         break;
557     if(n < macro->nargs) {
558       /* It is! */
559       if(e->nargs != 0)
560         fatal(0, "%s:%d: macro argument (%s) cannot take parameters",
561               name, e->line, e->name);
562       /* Replace it with the argument text */
563       e->type = ELEMENT_TEXT;
564       e->text = values[n];
565       continue;
566     }
567     /* It's not a macro argument.  We must recurse into its arguments to
568      * substitute the macro arguments. */
569     /* TODO */
570     /* In order to do this we must parse it and our callers must expect the
571      * parsed form.  We're not ready for this yet... */
572   }
573 }
574
575 static void cgi_expand_parsed(const char *name,
576                               struct cgi_element *head,
577                               const struct cgi_expansion *expansions,
578                               size_t nexpansions,
579                               cgi_sink *output,
580                               void *u) {
581   int n;
582   const struct cgi_macro *macro;
583   struct cgi_element *e, *macro_head;
584
585   for(e = head; e; e = e->next) {
586     switch(e->type) {
587     case ELEMENT_TEXT:
588       output->sink->write(output->sink, e->text, strlen(e->text));
589       break;
590     case ELEMENT_EXPANSION:
591       if((n = table_find(expansions,
592                          offsetof(struct cgi_expansion, name),
593                          sizeof (struct cgi_expansion),
594                          nexpansions,
595                          e->name)) >= 0) {
596         /* We found a built-in */
597         if(e->nargs < expansions[n].minargs)
598           fatal(0, "%s:%d: insufficient arguments to @%s@ (min %d, got %d)",
599                 name, e->line, e->name, expansions[n].minargs, e->nargs);
600         if(e->nargs > expansions[n].maxargs)
601           fatal(0, "%s:%d: too many arguments to @%s@ (max %d, got %d)",
602                 name, e->line, e->name, expansions[n].maxargs, e->nargs);
603         /* for ordinary expansions, recursively expand the arguments */
604         if(!(expansions[n].flags & EXP_MAGIC))
605           cgi_expand_all_args(name, e->line, expansions, nexpansions, u,
606                               e->args, e->nargs);
607         expansions[n].handler(e->nargs, e->args, output, u);
608       } else if(cgi_macros && (macro = hash_find(cgi_macros, e->name))) {
609         /* We found a macro */
610         if(e->nargs != macro->nargs)
611           fatal(0, "%s:%d: wrong number of arguments to @%s@ (need %d, got %d)",
612                 name, e->line, e->name, macro->nargs, e->nargs);
613         /* Expand arguments */
614         cgi_expand_all_args(name, e->line, expansions, nexpansions, u,
615                             e->args, e->nargs);
616         /* Parse the macro value.  Doing this every time isn't very efficient,
617          * but NB that we mess with the result of the parse, so for the time
618          * being we do need to do it. */
619         macro_head = cgi_parse_string(e->name, macro->value);
620         /* Substitute in argument values */
621         cgi_substitute_args(name, macro_head, macro, e->args);
622         /* Expand the result */
623         cgi_expand_parsed(e->name,
624                           macro_head,
625                           expansions,
626                           nexpansions,
627                           output,
628                           u);
629       } else {
630         /* Totally undefined */
631         fatal(0, "%s:%d: unknown expansion '%s'", name, e->line, e->name);
632       }
633       break;
634     }
635   }
636 }
637
638 char *cgi_makeurl(const char *url, ...) {
639   va_list ap;
640   struct kvp *kvp, *k, **kk = &kvp;
641   struct dynstr d;
642   const char *n, *v;
643   
644   dynstr_init(&d);
645   dynstr_append_string(&d, url);
646   va_start(ap, url);
647   while((n = va_arg(ap, const char *))) {
648     v = va_arg(ap, const char *);
649     *kk = k = xmalloc(sizeof *k);
650     kk = &k->next;
651     k->name = n;
652     k->value = v;
653   }
654   *kk = 0;
655   if(kvp) {
656     dynstr_append(&d, '?');
657     dynstr_append_string(&d, kvp_urlencode(kvp, 0));
658   }
659   dynstr_terminate(&d);
660   return d.vec;
661 }
662
663 void cgi_set_option(const char *name, const char *value) {
664   struct kvp *k = xmalloc(sizeof *k);
665
666   k->next = labels;
667   k->name = name;
668   k->value = value;
669   labels = k;
670 }
671
672 static void option_label(int attribute((unused)) nvec,
673                          char **vec) {
674   cgi_set_option(vec[0], vec[1]);
675 }
676
677 static void option_include(int attribute((unused)) nvec,
678                            char **vec) {
679   include_options(vec[0]);
680 }
681
682 static void option_columns(int nvec,
683                             char **vec) {
684   struct column *c = xmalloc(sizeof *c);
685   
686   c->next = columns;
687   c->name = vec[0];
688   c->ncolumns = nvec - 1;
689   c->columns = &vec[1];
690   columns = c;
691 }
692
693 static struct option {
694   const char *name;
695   int minargs, maxargs;
696   void (*handler)(int nvec, char **vec);
697 } options[] = {
698   { "columns", 1, INT_MAX, option_columns },
699   { "include", 1, 1, option_include },
700   { "label", 2, 2, option_label },
701 };
702
703 struct read_options_state {
704   const char *name;
705   int line;
706 };
707
708 static void read_options_error(const char *msg,
709                                void *u) {
710   struct read_options_state *cs = u;
711   
712   error(0, "%s:%d: %s", cs->name, cs->line, msg);
713 }
714
715 static void include_options(const char *name) {
716   int n, i;
717   int fd;
718   FILE *fp;
719   char **vec, *buffer;
720   struct read_options_state cs;
721
722   if((fd = template_open(name, "", &cs.name)) < 0) return;
723   if(!(fp = fdopen(fd, "r"))) fatal(errno, "error calling fdopen");
724   cs.line = 0;
725   while(!inputline(cs.name, fp, &buffer, '\n')) {
726     ++cs.line;
727     if(!(vec = split(buffer, &n, SPLIT_COMMENTS|SPLIT_QUOTES,
728                      read_options_error, &cs)))
729       continue;
730     if(!n) continue;
731     if((i = TABLE_FIND(options, struct option, name, vec[0])) == -1) {
732       error(0, "%s:%d: unknown option '%s'", cs.name, cs.line, vec[0]);
733       continue;
734     }
735     ++vec;
736     --n;
737     if(n < options[i].minargs) {
738       error(0, "%s:%d: too few arguments to '%s'", cs.name, cs.line, vec[-1]);
739       continue;
740     }
741     if(n > options[i].maxargs) {
742       error(0, "%s:%d: too many arguments to '%s'", cs.name, cs.line, vec[-1]);
743       continue;
744     }
745     options[i].handler(n, vec);
746   }
747   fclose(fp);
748 }
749
750 static void read_options(void) {
751   if(!have_read_options) {
752     have_read_options = 1;
753     include_options("options");
754   }
755 }
756
757 const char *cgi_label(const char *key) {
758   const char *label;
759
760   read_options();
761   if(!(label = kvp_get(labels, key))) {
762     /* No label found */
763     if(!strncmp(key, "images.", 7)) {
764       static const char *url_static;
765       /* images.X defaults to <url.static>X.png */
766
767       if(!url_static)
768         url_static = cgi_label("url.static");
769       byte_xasprintf((char **)&label, "%s%s.png", url_static, key + 7);
770     } else if((label = strchr(key, '.')))
771       /* X.Y defaults to Y */
772       ++label;
773     else
774       /* otherwise default to label name */
775       label = key;
776   }
777   return label;
778 }
779
780 int cgi_label_exists(const char *key) {
781   read_options();
782   return kvp_get(labels, key) ? 1 : 0;
783 }
784
785 char **cgi_columns(const char *name, int *ncolumns) {
786   struct column *c;
787
788   read_options();
789   for(c = columns; c && strcmp(name, c->name); c = c->next)
790     ;
791   if(c) {
792     if(ncolumns)
793       *ncolumns = c->ncolumns;
794     return c->columns;
795   } else {
796     if(ncolumns)
797       *ncolumns = 0;
798     return 0;
799   }
800 }
801
802 void cgi_define(const char *name,
803                 int nargs,
804                 char **args,
805                 const char *value) {
806   struct cgi_macro m;
807
808   if(!cgi_macros)
809     cgi_macros = hash_new(sizeof(struct cgi_macro));
810   m.nargs = nargs;
811   m.args = args;
812   m.value = value;
813   hash_add(cgi_macros, name, &m, HASH_INSERT_OR_REPLACE);
814 }
815
816 /*
817 Local Variables:
818 c-basic-offset:2
819 comment-column:40
820 End:
821 */