2 * This file is part of DisOrder.
3 * Copyright (C) 2004, 2005, 2006, 2008 Richard Kettlewell
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.
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.
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
42 #include "configuration.h"
48 #include "inputline.h"
67 #define RELIST(x) struct re *x, **x##_tail = &x
69 static int have_read_options
;
70 static struct kvp
*labels
;
71 static struct column
*columns
;
73 static void include_options(const char *name
);
75 static void cgi_parse_get(void) {
78 if(!(q
= getenv("QUERY_STRING"))) fatal(0, "QUERY_STRING not set");
79 cgi_args
= kvp_urldecode(q
, strlen(q
));
82 static void cgi_input(char **ptrp
, size_t *np
) {
88 if(!(cl
= getenv("CONTENT_LENGTH"))) fatal(0, "CONTENT_LENGTH not set");
90 q
= xmalloc_noptr(n
+ 1);
92 r
= read(0, q
+ m
, n
- m
);
96 fatal(0, "unexpected end of file reading request body");
99 default: fatal(errno
, "error reading request body");
102 if(memchr(q
, 0, n
)) fatal(0, "null character in request body");
108 static int cgi_field_callback(const char *name
, const char *value
,
110 char *disposition
, *pname
, *pvalue
;
113 if(!strcmp(name
, "content-disposition")) {
114 if(mime_rfc2388_content_disposition(value
,
118 fatal(0, "error parsing Content-Disposition field");
119 if(!strcmp(disposition
, "form-data")
121 && !strcmp(pname
, "name")) {
123 fatal(0, "duplicate Content-Disposition field");
130 static int cgi_part_callback(const char *s
,
131 void attribute((unused
)) *u
) {
135 if(!(s
= mime_parse(s
, cgi_field_callback
, &name
)))
136 fatal(0, "error parsing part header");
137 if(!name
) fatal(0, "no name found");
138 k
= xmalloc(sizeof *k
);
146 static void cgi_parse_multipart(const char *boundary
) {
150 if(mime_multipart(q
, cgi_part_callback
, boundary
, 0))
151 fatal(0, "invalid multipart object");
154 static void cgi_parse_post(void) {
156 char *q
, *type
, *pname
, *pvalue
;
159 if(!(ct
= getenv("CONTENT_TYPE")))
160 ct
= "application/x-www-form-urlencoded";
161 if(mime_content_type(ct
, &type
, &pname
, &pvalue
))
162 fatal(0, "invalid content type '%s'", ct
);
163 if(!strcmp(type
, "application/x-www-form-urlencoded")) {
165 cgi_args
= kvp_urldecode(q
, n
);
168 if(!strcmp(type
, "multipart/form-data")) {
169 if(!pname
|| strcmp(pname
, "boundary"))
170 fatal(0, "expected a boundary parameter, found %s",
171 pname ? pname
: "nothing");
172 cgi_parse_multipart(pvalue
);
175 fatal(0, "unrecognized content type '%s'", type
);
178 void cgi_parse(void) {
182 if(!(p
= getenv("REQUEST_METHOD"))) fatal(0, "REQUEST_METHOD not set");
183 if(!strcmp(p
, "GET"))
185 else if(!strcmp(p
, "POST"))
188 fatal(0, "unknown request method %s", p
);
189 for(k
= cgi_args
; k
; k
= k
->next
)
190 if(!utf8_valid(k
->name
, strlen(k
->name
))
191 || !utf8_valid(k
->value
, strlen(k
->value
)))
192 fatal(0, "invalid UTF-8 sequence in cgi argument");
195 const char *cgi_get(const char *name
) {
196 return kvp_get(cgi_args
, name
);
199 void cgi_output(cgi_sink
*output
, const char *fmt
, ...) {
205 n
= byte_vasprintf(&r
, fmt
, ap
);
207 fatal(errno
, "error calling byte_vasprintf");
209 r
= cgi_sgmlquote(r
, 0);
210 output
->sink
->write(output
->sink
, r
, strlen(r
));
214 void cgi_header(struct sink
*output
, const char *name
, const char *value
) {
215 sink_printf(output
, "%s: %s\r\n", name
, value
);
218 void cgi_body(struct sink
*output
) {
219 sink_printf(output
, "\r\n");
222 char *cgi_sgmlquote(const char *s
, int raw
) {
223 uint32_t *ucs
, *p
, c
;
228 if(!(ucs
= utf8_to_utf32(s
, strlen(s
), 0))) exit(EXIT_FAILURE
);
230 ucs
= xmalloc_noptr((strlen(s
) + 1) * sizeof(uint32_t));
231 for(n
= 0; s
[n
]; ++n
)
232 ucs
[n
] = (unsigned char)s
[n
];
237 /* estimate the length we'll need */
238 for(p
= ucs
; (c
= *p
); ++p
) {
241 if(c
> 127 || c
< 32) {
252 /* format the string */
253 b
= bp
= xmalloc_noptr(n
);
254 for(p
= ucs
; (c
= *p
); ++p
) {
257 if(*p
> 127 || *p
< 32) {
262 bp
+= sprintf(bp
, "&#%lu;", (unsigned long)c
);
272 void cgi_attr(struct sink
*output
, const char *name
, const char *value
) {
273 if(!value
[strspn(value
, "abcdefghijklmnopqrstuvwxyz"
274 "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
276 sink_printf(output
, "%s=%s", name
, value
);
278 sink_printf(output
, "%s=\"%s\"", name
, cgi_sgmlquote(value
, 0));
281 void cgi_opentag(struct sink
*output
, const char *name
, ...) {
285 sink_printf(output
, "<%s", name
);
287 while((n
= va_arg(ap
, const char *))) {
288 sink_printf(output
, " ");
289 v
= va_arg(ap
, const char *);
291 cgi_attr(output
, n
, v
);
293 sink_printf(output
, n
);
295 sink_printf(output
, ">");
298 void cgi_closetag(struct sink
*output
, const char *name
) {
299 sink_printf(output
, "</%s>", name
);
302 static int template_open(const char *name
,
304 const char **filenamep
) {
309 dirs
[0] = pkgconfdir
;
310 dirs
[1] = pkgdatadir
;
312 if((fd
= open(name
, O_RDONLY
)) < 0) fatal(0, "cannot open %s", name
);
315 for(n
= 0; n
< config
->templates
.n
+ (int)(sizeof dirs
/ sizeof *dirs
); ++n
) {
316 byte_xasprintf(&fullpath
, "%s/%s%s",
317 n
< config
->templates
.n ? config
->templates
.s
[n
]
318 : dirs
[n
- config
->templates
.n
],
320 if((fd
= open(fullpath
, O_RDONLY
)) >= 0) break;
322 if(fd
< 0) error(0, "cannot find %s%s in template path", name
, ext
);
323 *filenamep
= fullpath
;
328 static int valid_template_name(const char *name
) {
329 if(strchr(name
, '/') || name
[0] == '.')
334 void cgi_expand(const char *template,
335 const struct cgi_expansion
*expansions
,
345 if(!valid_template_name(template))
346 fatal(0, "invalid template name '%s'", template);
347 if((fd
= template_open(template, ".html", &template)) < 0)
348 exitfn(EXIT_FAILURE
);
349 if(fstat(fd
, &sb
) < 0) fatal(errno
, "cannot stat %s", template);
351 b
= xmalloc_noptr(sb
.st_size
+ 1);
352 while(m
< sb
.st_size
) {
353 n
= read(fd
, b
+ m
, sb
.st_size
- m
);
355 else if(n
== 0) fatal(0, "unexpected EOF reading %s", template);
356 else if(errno
!= EINTR
) fatal(errno
, "error reading %s", template);
360 cgi_expand_string(template, b
, expansions
, nexpansions
, output
, u
);
363 void cgi_expand_string(const char *name
,
364 const char *template,
365 const struct cgi_expansion
*expansions
,
369 int braces
, n
, m
, line
= 1, sline
;
374 cgi_sink parameter_output
;
377 if(*template != '@') {
379 while(*p
&& *p
!= '@') {
380 if(*p
== '\n') ++line
;
383 output
->sink
->write(output
->sink
, template, p
- template);
391 while(*template != '@') {
393 if(*template == '{') {
396 while(*template && (*template != '}' || braces
> 0)) {
398 case '{': ++braces
; break;
399 case '}': --braces
; break;
400 case '\n': ++line
; break;
402 dynstr_append(&d
, *template++);
404 if(!*template) fatal(0, "%s:%d: unterminated expansion", name
, sline
);
406 /* skip whitespace after closing bracket */
407 while(isspace((unsigned char)*template))
410 /* unbracketed arg */
411 /* leading whitespace is not significant in unquoted args */
412 while(isspace((unsigned char)*template))
415 && *template != '@' && *template != '{' && *template != ':') {
416 if(*template == '\n') ++line
;
417 dynstr_append(&d
, *template++);
421 if(!*template) fatal(0, "%s:%d: unterminated expansion", name
, sline
);
422 /* trailing whitespace is not significant in unquoted args */
423 while(d
.nvec
&& (isspace((unsigned char)d
.vec
[d
.nvec
- 1])))
426 dynstr_terminate(&d
);
427 vector_append(&v
, d
.vec
);
430 vector_terminate(&v
);
431 /* @@ terminates this file */
434 if((n
= table_find(expansions
,
435 offsetof(struct cgi_expansion
, name
),
436 sizeof (struct cgi_expansion
),
439 fatal(0, "%s:%d: unknown expansion '%s'", name
, line
, v
.vec
[0]);
440 if(v
.nvec
- 1 < expansions
[n
].minargs
)
441 fatal(0, "%s:%d: insufficient arguments to @%s@ (min %d, got %d)",
442 name
, line
, v
.vec
[0], expansions
[n
].minargs
, v
.nvec
- 1);
443 if(v
.nvec
- 1 > expansions
[n
].maxargs
)
444 fatal(0, "%s:%d: too many arguments to @%s@ (max %d, got %d)",
445 name
, line
, v
.vec
[0], expansions
[n
].maxargs
, v
.nvec
- 1);
446 /* for ordinary expansions, recursively expand the arguments */
447 if(!(expansions
[n
].flags
& EXP_MAGIC
)) {
448 for(m
= 1; m
< v
.nvec
; ++m
) {
450 byte_xasprintf(&argname
, "<%s:%d arg #%d>", name
, sline
, m
);
451 parameter_output
.quote
= 0;
452 parameter_output
.sink
= sink_dynstr(&d
);
453 cgi_expand_string(argname
, v
.vec
[m
],
454 expansions
, nexpansions
,
455 ¶meter_output
, u
);
456 dynstr_terminate(&d
);
460 expansions
[n
].handler(v
.nvec
- 1, v
.vec
+ 1, output
, u
);
464 char *cgi_makeurl(const char *url
, ...) {
466 struct kvp
*kvp
, *k
, **kk
= &kvp
;
471 dynstr_append_string(&d
, url
);
473 while((n
= va_arg(ap
, const char *))) {
474 v
= va_arg(ap
, const char *);
475 *kk
= k
= xmalloc(sizeof *k
);
482 dynstr_append(&d
, '?');
483 dynstr_append_string(&d
, kvp_urlencode(kvp
, 0));
485 dynstr_terminate(&d
);
489 void cgi_set_option(const char *name
, const char *value
) {
490 struct kvp
*k
= xmalloc(sizeof *k
);
498 static void option_label(int attribute((unused
)) nvec
,
500 cgi_set_option(vec
[0], vec
[1]);
503 static void option_include(int attribute((unused
)) nvec
,
505 include_options(vec
[0]);
508 static void option_columns(int nvec
,
510 struct column
*c
= xmalloc(sizeof *c
);
514 c
->ncolumns
= nvec
- 1;
515 c
->columns
= &vec
[1];
519 static struct option
{
521 int minargs
, maxargs
;
522 void (*handler
)(int nvec
, char **vec
);
524 { "columns", 1, INT_MAX
, option_columns
},
525 { "include", 1, 1, option_include
},
526 { "label", 2, 2, option_label
},
529 struct read_options_state
{
534 static void read_options_error(const char *msg
,
536 struct read_options_state
*cs
= u
;
538 error(0, "%s:%d: %s", cs
->name
, cs
->line
, msg
);
541 static void include_options(const char *name
) {
546 struct read_options_state cs
;
548 if((fd
= template_open(name
, "", &cs
.name
)) < 0) return;
549 if(!(fp
= fdopen(fd
, "r"))) fatal(errno
, "error calling fdopen");
551 while(!inputline(cs
.name
, fp
, &buffer
, '\n')) {
553 if(!(vec
= split(buffer
, &n
, SPLIT_COMMENTS
|SPLIT_QUOTES
,
554 read_options_error
, &cs
)))
557 if((i
= TABLE_FIND(options
, struct option
, name
, vec
[0])) == -1) {
558 error(0, "%s:%d: unknown option '%s'", cs
.name
, cs
.line
, vec
[0]);
563 if(n
< options
[i
].minargs
) {
564 error(0, "%s:%d: too few arguments to '%s'", cs
.name
, cs
.line
, vec
[-1]);
567 if(n
> options
[i
].maxargs
) {
568 error(0, "%s:%d: too many arguments to '%s'", cs
.name
, cs
.line
, vec
[-1]);
571 options
[i
].handler(n
, vec
);
576 static void read_options(void) {
577 if(!have_read_options
) {
578 have_read_options
= 1;
579 include_options("options");
583 const char *cgi_label(const char *key
) {
587 if(!(label
= kvp_get(labels
, key
))) {
589 if(!strncmp(key
, "images.", 7)) {
590 static const char *url_static
;
591 /* images.X defaults to <url.static>X.png */
594 url_static
= cgi_label("url.static");
595 byte_xasprintf((char **)&label
, "%s%s.png", url_static
, key
+ 7);
596 } else if((label
= strchr(key
, '.')))
597 /* X.Y defaults to Y */
600 /* otherwise default to label name */
606 int cgi_label_exists(const char *key
) {
608 return kvp_get(labels
, key
) ?
1 : 0;
611 char **cgi_columns(const char *name
, int *ncolumns
) {
615 for(c
= columns
; c
&& strcmp(name
, c
->name
); c
= c
->next
)
619 *ncolumns
= c
->ncolumns
;