disable recursion with a -
[tridge/junkcode.git] / tserver / cgi.c
1 /* 
2    some simple CGI helper routines
3    Copyright (C) Andrew Tridgell 1997-2001
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,
11    but WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13    GNU 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., 675 Mass Ave, Cambridge, MA 02139, USA.
18 */
19
20
21 #include "includes.h"
22
23 #define CONTENT_DISPOSITION "Content-Disposition:"
24 #define CONTENT_TYPE "Content-Type:"
25 #define MULTIPART_FORM_DATA "multipart/form-data"
26 #define CRLF "\r\n"
27
28 /* 
29    inplace handling of + and % escapes in http variables 
30 */
31 static void unescape(char *p)
32 {
33         unsigned v;
34
35         while (*p) {
36                 if (*p == '+') *p = ' ';
37                 if (*p == '%' && sscanf(p+1, "%02x", &v) == 1) {
38                         *p = (char)v;
39                         memcpy(p+1, p+3, strlen(p+3)+1);
40                 }
41                 p++;
42         }
43 }
44
45 /*
46   read one line from a file, allocating space as need be
47   adjust length on return
48 */
49 static char *grab_line(FILE *f, const char *terminator, int *length)
50 {
51         int len = 1024;
52         char *ret = malloc(len);
53         int i = 0;
54         int tlen = strlen(terminator);
55
56         while (*length) {
57                 int c;
58         
59                 if (i == len) {
60                         len *= 2;
61                         ret = realloc(ret, len);
62                 }
63         
64                 c = fgetc(f);
65                 (*length)--;
66
67                 if (c == EOF) {
68                         (*length) = 0;
69                         break;
70                 }
71                 
72                 ret[i++] = c;
73
74                 if (memcmp(terminator, &ret[i-tlen], tlen) == 0) {
75                         i -= tlen;
76                         break;
77                 }
78         }
79
80         ret[i] = 0;
81         return ret;
82 }
83
84
85 /*
86   add a name/value pair to the list of cgi variables 
87 */
88 static void put(struct cgi_state *cgi, const char *name, const char *value)
89 {
90         struct cgi_var *var;
91         int len;
92         char *cgi_name, *p;
93
94         if (!name || !value) return;
95
96         var = malloc(sizeof(*var));
97         memset(var, 0, sizeof(*var));
98         var->next = cgi->variables;
99
100         /* trim leading spaces */
101         while (*name && (*name == '+' || *name == ' ')) name++;
102
103         var->name = strdup(name);
104         var->value = strdup(value);
105         unescape(var->name);
106         unescape(var->value);
107
108         /* trim trailing spaces */
109         len = strlen(var->value);
110         while (len && isspace(var->value[len-1])) {
111                 var->value[len-1] = 0;
112                 len--;
113         }
114
115         for (p=var->name; *p; p++) {
116                 if (!isalnum(*p) && !strchr("_-", *p)) {
117                         *p = '_';
118                 }
119         }
120
121         cgi->variables = var;
122         asprintf(&cgi_name, "CGI_%s", var->name);
123         cgi->tmpl->put(cgi->tmpl, cgi_name, var->value, NULL);
124         free(cgi_name);
125 }
126
127
128 /*
129   parse a url encoded form
130 */
131 static void load_urlencoded(struct cgi_state *cgi)
132 {
133         int len = cgi->content_length;
134         char *line;
135         char *p;
136         FILE *f = stdin;
137
138         while (len && (line=grab_line(f, "&", &len))) {
139                 p = strchr(line,'=');
140                 if (p) {
141                         *p = 0;
142                         put(cgi, line, p+1);
143                 }
144                 free(line);
145         }
146 }
147
148 /*
149   parse a single element of a multipart encoded form
150   It's rather more complex than I would like :(
151 */
152 static int load_one_part(struct cgi_state *cgi, FILE *f, int *len, char *boundary)
153 {
154         char *line;
155         char *name=NULL;
156         char *content;
157         char *filename=NULL;
158         unsigned content_len=0, content_alloc=1024;
159         unsigned boundary_len = strlen(boundary);
160         int c;
161         int raw_data = 0;
162
163         while (*len && (line=grab_line(f, CRLF, len))) {
164                 if (*line == 0) break;
165                 if (strcmp(line,"--") == 0) return 1;
166                 if (strncasecmp(line, CONTENT_TYPE, 
167                                 strlen(CONTENT_TYPE)) == 0) {
168                         raw_data = 1;
169                 }
170                 if (strncasecmp(line, CONTENT_DISPOSITION, 
171                                 strlen(CONTENT_DISPOSITION)) == 0) {
172                         char *p = strstr(line,"; name=");
173                         if (!p) continue;
174                         p += 7;
175                         if (*p == '"') p++;
176                         name = strndup(p, strcspn(p, "\";"));
177                         p = strstr(line,"; filename=\"");
178                         if (p) {
179                                 p += 12;
180                                 filename = strndup(p, strcspn(p, "\";"));
181                         }
182                 }
183         }
184         
185         content = malloc(content_alloc);
186         
187         while (*len && (c = fgetc(f)) != EOF) {
188                 (*len)--;
189                 if (content_len >= (content_alloc-1)) {
190                         content_alloc *= 2;
191                         content = realloc(content, content_alloc);
192                 }
193                 content[content_len++] = c;
194                 /* we keep grabbing content until we hit a boundary */
195                 if (memcmp(boundary, &content[content_len-boundary_len], 
196                            boundary_len) == 0 &&
197                     memcmp("--", &content[content_len-boundary_len-2], 2) == 0) {
198                         content_len -= boundary_len+4;
199                         if (name) {
200                                 if (raw_data) {
201                                         put(cgi, name, filename?filename:"");
202                                         cgi->variables->content = content;
203                                         cgi->variables->content_len = content_len;
204                                 } else {
205                                         content[content_len] = 0;
206                                         put(cgi, name, content);
207                                         free(name);
208                                         free(content);
209                                 }
210                         } else {
211                                 free(content);
212                         }
213                         fgetc(f); fgetc(f);
214                         (*len) -= 2;
215                         return 0;
216                 }
217         }
218
219         if (filename) free(filename);
220
221         return 1;
222 }
223
224 /*
225   parse a multipart encoded form (for file upload)
226   see rfc1867
227 */
228 static void load_multipart(struct cgi_state *cgi)
229 {
230         char *boundary;
231         FILE *f = stdin;
232         int len = cgi->content_length;
233         char *line;
234
235         if (!cgi->content_type) return;
236         boundary = strstr(cgi->content_type, "boundary=");
237         if (!boundary) return;
238         boundary += 9;
239         trim_tail(boundary, CRLF);
240         line = grab_line(f, CRLF, &len);
241         if (strncmp(line,"--", 2) != 0 || 
242             strncmp(line+2,boundary,strlen(boundary)) != 0) {
243                 fprintf(stderr,"Malformed multipart?\n");
244                 free(line);
245                 return;
246         }
247
248         if (strcmp(line+2+strlen(boundary), "--") == 0) {
249                 /* the end is only the beginning ... */
250                 free(line);
251                 return;
252         }
253
254         free(line);
255         while (load_one_part(cgi, f, &len, boundary) == 0) ;
256 }
257
258 /*
259   load all the variables passed to the CGI program. May have multiple variables
260   with the same name and the same or different values. 
261 */
262 static void load_variables(struct cgi_state *cgi)
263 {
264         char *p, *s, *tok;
265
266         if (cgi->content_length > 0 && cgi->request_post) {
267                 if (strncmp(cgi->content_type, MULTIPART_FORM_DATA, 
268                             strlen(MULTIPART_FORM_DATA)) == 0) {
269                         load_multipart(cgi);
270                 } else {
271                         load_urlencoded(cgi);
272                 }
273         }
274
275         if ((s=cgi->query_string)) {
276                 char *pp;
277                 for (tok=strtok_r(s,"&;", &pp);tok;tok=strtok_r(NULL,"&;", &pp)) {
278                         p = strchr(tok,'=');
279                         if (p) {
280                                 *p = 0;
281                                 put(cgi, tok, p+1);
282                         }
283                 }
284         }
285 }
286
287
288 /*
289   find a variable passed via CGI
290   Doesn't quite do what you think in the case of POST text variables, because
291   if they exist they might have a value of "" or even " ", depending on the 
292   browser. Also doesn't allow for variables[] containing multiple variables
293   with the same name and the same or different values.
294 */
295 static const char *get(struct cgi_state *cgi, const char *name)
296 {
297         struct cgi_var *var;
298
299         for (var = cgi->variables; var; var = var->next) {
300                 if (strcmp(var->name, name) == 0) {
301                         return var->value;
302                 }
303         }
304         return NULL;
305 }
306
307 /*
308    return the content of a binary cgi variable (for file upload)
309 */
310 static const char *get_content(struct cgi_state *cgi, const char *name, unsigned *size)
311 {
312         struct cgi_var *var;
313
314         for (var = cgi->variables; var; var = var->next) {
315                 if (strcmp(var->name, name) == 0) {
316                         *size = var->content_len;
317                         return var->content;
318                 }
319         }
320         return NULL;
321 }
322
323 /*
324   tell a browser about a fatal error in the http processing
325 */
326 static void http_error(struct cgi_state *cgi, 
327                        const char *err, const char *header, const char *info)
328 {
329         if (!cgi->got_request) {
330                 /* damn browsers don't like getting cut off before they give a request */
331                 char line[1024];
332                 while (fgets(line, sizeof(line)-1, stdin)) {
333                         if (strncasecmp(line,"GET ", 4)==0 || 
334                             strncasecmp(line,"POST ", 5)==0 ||
335                             strncasecmp(line,"PUT ", 4)==0) {
336                                 break;
337                         }
338                 }
339         }
340
341         printf("HTTP/1.0 %s\r\n%sConnection: close\r\nContent-Type: text/html\r\n\r\n<HTML><HEAD><TITLE>%s</TITLE></HEAD><BODY><H1>%s</H1>%s<p></BODY></HTML>\r\n\r\n", err, header, err, err, info);
342 }
343
344 /*
345   send a http header based on file extension
346 */
347 static enum MIME_TYPE http_header(struct cgi_state *cgi, const char *filename)
348 {
349         int i;
350         static struct {
351                 char *pattern;
352                 char *mime_type;
353                 enum MIME_TYPE type;
354         } mime_types[] = {
355                 {"*.gif",  "image/gif",  MIME_TYPE_IMAGE_GIF},
356                 {"*.jpg",  "image/jpeg", MIME_TYPE_IMAGE_JPEG},
357                 {"*.txt",  "text/plain", MIME_TYPE_TEXT_PLAIN},
358                 {"*.html", "text/html",  MIME_TYPE_TEXT_HTML},
359                 {NULL,     "data",       MIME_TYPE_UNKNOWN},
360         };
361
362         printf("HTTP/1.0 200 OK\r\nConnection: close\r\n");
363
364         for (i=0; mime_types[i].pattern; i++) {
365                 if (fnmatch(mime_types[i].pattern, filename, 0) == 0) break;
366         }
367         printf("Content-Type: %s\r\n\r\n", mime_types[i].mime_type);
368         return mime_types[i].type;
369 }
370
371
372 /*
373   handle a file download
374 */
375 static void download(struct cgi_state *cgi, const char *path)
376 {
377         enum MIME_TYPE mtype;
378         size_t size;
379         void *m;
380
381         mtype = cgi->http_header(cgi, path);
382
383         if (mtype == MIME_TYPE_TEXT_HTML) {
384                 cgi->tmpl->process(cgi->tmpl, path, 1);
385                 return;
386         }
387
388         m = map_file(path, &size);
389         if (m) {
390                 fwrite(m, 1, size, stdout);
391                 unmap_file(m, size);
392         }
393 }
394
395
396 /* we're running under a web server as cgi */
397 static int setup_cgi(struct cgi_state *cgi)
398 {
399         char *p;
400
401         if ((p = getenv("CONTENT_LENGTH"))) {
402                 cgi->content_length = atoi(p);
403         }
404         if ((p = getenv("REQUEST_METHOD"))) {
405                 cgi->got_request = 1;
406                 if (strcasecmp(p, "POST") == 0) {
407                         cgi->request_post = 1;
408                 }
409         }
410         if ((p = getenv("QUERY_STRING"))) {
411                 cgi->query_string = strdup(p);
412         }
413         if ((p = getenv("SCRIPT_NAME"))) {
414                 cgi->url = strdup(p);
415                 cgi->pathinfo = cgi->url;
416         }
417         if ((p = getenv("CONTENT_TYPE"))) {
418                 cgi->content_type = strdup(p);
419         }
420         return 0;
421 }
422
423
424
425 /* we are a mini-web server. We need to read the request from stdin */
426 static int setup_standalone(struct cgi_state *cgi)
427 {
428         char line[1024];
429         char *url=NULL;
430         char *p;
431
432         while (fgets(line, sizeof(line)-1, stdin)) {
433                 trim_tail(line, CRLF);
434                 if (line[0] == 0) break;
435                 if (strncasecmp(line,"GET ", 4)==0) {
436                         cgi->got_request = 1;
437                         url = strdup(&line[4]);
438                 } else if (strncasecmp(line,"POST ", 5)==0) {
439                         cgi->got_request = 1;
440                         cgi->request_post = 1;
441                         url = strdup(&line[5]);
442                 } else if (strncasecmp(line,"PUT ", 4)==0) {
443                         cgi->got_request = 1;
444                         cgi->http_error(cgi, "400 Bad Request", "",
445                                         "This server does not accept PUT requests");
446                         return -1;
447                 } else if (strncasecmp(line,"Content-Length: ", 16)==0) {
448                         cgi->content_length = atoi(&line[16]);
449                 } else if (strncasecmp(line,"Content-Type: ", 14)==0) {
450                         cgi->content_type = strdup(&line[14]);
451                 }
452                 /* ignore all other requests! */
453         }
454
455         if (!url) {
456                 cgi->http_error(cgi, "400 Bad Request", "",
457                                 "You must specify a GET or POST request");
458                 exit(1);
459         }
460
461         /* trim the URL */
462         if ((p = strchr(url,' ')) || (p=strchr(url,'\t'))) {
463                 *p = 0;
464         }
465
466         /* anything following a ? in the URL is part of the query string */
467         if ((p=strchr(url,'?'))) {
468                 cgi->query_string = p+1;
469                 *p = 0;
470         }
471
472         cgi->url = url;
473         cgi->pathinfo = url;
474         return 0;
475 }
476
477 /*
478   read and parse the http request
479  */
480 static int setup(struct cgi_state *cgi)
481 {
482         int ret;
483
484         if (getenv("GATEWAY_INTERFACE")) {
485                 ret = setup_cgi(cgi);
486         } else {
487                 ret = setup_standalone(cgi);
488         }
489
490         while (cgi->pathinfo && *cgi->pathinfo == '/') cgi->pathinfo++;
491
492         return ret;
493 }
494
495 /*
496   destroy a open cgi state
497 */
498 static void destroy(struct cgi_state *cgi)
499 {
500         cgi->tmpl->destroy(cgi->tmpl);
501
502         while (cgi->variables) {
503                 struct cgi_var *var = cgi->variables;
504                 free(var->name);
505                 free(var->value);
506                 cgi->variables = cgi->variables->next;
507                 free(var);
508         }
509
510         free(cgi->url);
511         memset(cgi, 0, sizeof(cgi));
512         free(cgi);
513 }
514
515 static struct cgi_state cgi_base = {
516         /* methods */
517         setup,
518         destroy,
519         http_header,
520         load_variables,
521         get,
522         get_content,
523         http_error,
524         download,
525         
526         /* rest are zero */
527 };
528
529 struct cgi_state *cgi_init(void)
530 {
531         struct cgi_state *cgi;
532
533         cgi = malloc(sizeof(*cgi));
534         memcpy(cgi, &cgi_base, sizeof(*cgi));
535
536         cgi->tmpl = template_init();
537
538         return cgi;
539 }