Be less verbose
[infodrom/cgilib] / cgi.c
1 /*
2     cgi.c - Some simple routines for CGI programming
3     Copyright (c) 1996-9,2007  Martin Schulze <joey@infodrom.org>
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., 59 Temple Place - Suite 330, Boston, MA 02111, USA.
18  */
19
20 #define _GNU_SOURCE 1
21
22 #include <stdio.h>
23 #include <stdlib.h>
24 #include <unistd.h>
25 #include <string.h>
26 #include <strings.h>
27 #include <ctype.h>
28 #include <syslog.h>
29 #include <cgi.h>
30 #include "aux.h"
31
32 char *cgiHeaderString = NULL;
33 char *cgiType = NULL;
34
35 extern s_cookie **cgiReadCookies();
36
37 int cgiSetHeader (char *name, char *value)
38 {
39     char *cp, *vp, *pivot;
40     int len;
41
42     if (!name || !strlen (name) || !value || !strlen (value))
43         return 0;
44     
45     for (cp=name;*cp && *cp!=' ' && *cp!='\r' && *cp!='\n' && *cp!=':';cp++);
46     for (vp=value;*vp && *vp!='\r' && *vp!='\n';vp++);
47
48     if (cgiHeaderString) {
49         len = (strlen (cgiHeaderString) + cp-name + vp-value + 5) * sizeof (char);
50         if ((pivot = (char *)realloc (cgiHeaderString,len)) == NULL)
51             return 0;
52         cgiHeaderString = pivot;
53         pivot += strlen (cgiHeaderString);
54     } else {
55         len = (cp-name + vp-value + 5) * sizeof (char);
56         if ((cgiHeaderString = (char *)malloc (len)) == NULL)
57             return 0;
58         pivot = cgiHeaderString;
59     }
60     strncpy (pivot, name, cp-name);
61     strncat (pivot, ": ", 2);
62     strncat (pivot, value, vp-value);
63     strncat (pivot, "\r\n", 2);
64
65     return 1;
66 }
67
68 int cgiSetType (char *type)
69 {
70     int len;
71     char *cp;
72
73     if (!type || !strlen (type))
74         return 0;
75     if (cgiType)
76         free (cgiType);
77
78     for (cp=type;*cp && *cp!='\r' && *cp!='\n';cp++);
79
80     len = (cp-type+1) * sizeof (char);
81     if ((cgiType = (char *)malloc (len+20)) == NULL)
82         return 0;
83     memset (cgiType, 0, len);
84     strncpy (cgiType, type, cp-type);
85     
86     return 1;
87 }
88
89 void cgiHeader ()
90 {
91     if (cgiType)
92         printf ("Content-type: %s\r\n", cgiType);
93     else
94         printf ("Content-type: text/html\r\n");
95     if (cgiHeaderString)
96         printf ("%s", cgiHeaderString);
97     printf ("\r\n");
98 }
99
100 void cgiDebug (int level, int where)
101 {
102     if (level > 0)
103         cgiDebugLevel = level;
104     else
105         cgiDebugLevel = 0;
106     if (where > 0) {
107         if (where < 3) {
108             cgiDebugType = where;
109             if (where == 2)
110                 openlog("cgilib", LOG_PID, LOG_USER);
111         } else
112             cgiDebugType = 0;
113     }
114 }
115
116 char *cgiDecodeString (char *text)
117 {
118     char *cp, *xp;
119
120     for (cp=text,xp=text; *cp; cp++) {
121         if (*cp == '%') {
122             if (strchr("0123456789ABCDEFabcdef", *(cp+1))
123                 && strchr("0123456789ABCDEFabcdef", *(cp+2))) {
124                 if (islower(*(cp+1)))
125                     *(cp+1) = toupper(*(cp+1));
126                 if (islower(*(cp+2)))
127                     *(cp+2) = toupper(*(cp+2));
128                 *(xp) = (*(cp+1) >= 'A' ? *(cp+1) - 'A' + 10 : *(cp+1) - '0' ) * 16
129                     + (*(cp+2) >= 'A' ? *(cp+2) - 'A' + 10 : *(cp+2) - '0');
130                 xp++;cp+=2;
131             }
132         } else {
133             *(xp++) = *cp;
134         }
135     }
136     memset(xp, 0, cp-xp);
137     return text;
138 }
139
140 /* cgiReadMultipart()
141  *
142  * Decode multipart/form-data
143  */
144 #define MULTIPART_DELTA 5
145 s_cgi *cgiReadMultipart (char *boundary)
146 {
147     char *line;
148     char *cp, *xp;
149     char *name = NULL, *type = NULL;
150     int header = 1;
151     s_var **result = NULL;
152     s_var **tmp;
153     int numresults = 0, current = 0;
154     int index = 0;
155     size_t len;
156     s_cgi *res;
157     
158     while ((line = cgiGetLine (stdin)) != NULL) {
159
160         if (!strncmp (line, boundary, strlen(boundary))) {
161             header = 1;
162         } else if (!strncasecmp (line, "Content-Disposition: form-data; ", 32)) {
163             if (!name) {
164                 if ((cp = strstr (line, "name=\"")) == NULL)
165                     continue;
166                 cp += 6;
167                 if ((xp = strchr (cp, '\"')) == NULL)
168                     continue;
169                 name = strndup (cp, xp-cp);
170                 cgiDebugOutput (2, "Found field name %s", name);
171             }
172         } else if (!strncasecmp (line, "Content-Type: ", 14)) {
173             if (!type) {
174                 cp = line + 14;
175                 type = strdup (cp);
176                 cgiDebugOutput (2, "Found mime type %s", type);
177             }
178         } else if (header) {
179             if (!strlen(line))
180                 header = 0;
181         } else {
182             if (name) {
183
184                 /* try to find out if there's already such a variable */
185                 for (index=0; index<current && strcmp (result[index]->name,name); index++);
186
187                 if (index == current) {
188                     if (!result) {
189                         len = MULTIPART_DELTA * sizeof (s_var *);
190                         if ((result = (s_var **)malloc (len)) == NULL) {
191                             if (type)
192                                 free (type);
193                             return NULL;
194                         }
195                         numresults = MULTIPART_DELTA;
196                         memset (result, 0, len);
197                         current = 0;
198                     } else {
199                         if (current+2 > numresults) {
200                             len = (numresults + MULTIPART_DELTA) * sizeof(s_var *);
201                             if ((tmp = (s_var **)realloc (result, len)) == NULL) {
202                                 for (index=0; result[index]; index++)
203                                     free (result[index]);
204                                 free (result);
205                                 free (name);
206                                 if (type)
207                                     free (type);
208                                 return NULL;
209                             }
210                             result = tmp;
211                             memset (result + numresults, 0, len - numresults*sizeof(s_var *));
212                             numresults += MULTIPART_DELTA;
213                         }
214                     }
215                     if ((result[current] = (s_var *)malloc(sizeof(s_var))) == NULL) {
216                         for (index=0; result[index]; index++)
217                             free (result[index]);
218                         free (result);
219                         free (name);
220                         if (type)
221                             free (type);
222                         return NULL;
223                     }
224                     current++;
225                     cgiDebugOutput (3, "Set #%d to %s=%s", index, name, line);
226                     result[index]->name = name; name = NULL;
227                     result[index]->value = strdup (line);
228                     if (type) {
229                         free (type);
230                         type = NULL;
231                     }
232                 } else {
233                     cgiDebugOutput (3, "Set #%d to %s=%s", index, name, line);
234                     free (name);
235                     if ((name = (char *)realloc (result[index]->value, strlen(result[index]->value)+strlen(line)+2)) != NULL) {
236                         strcat(name, "\n");
237                         strcat(name, line);
238                         result[index]->value = name;
239                         if (type)
240                             free (type);
241                         name = type = NULL;
242                     }
243                 }
244             } else {
245                 if (index > 0) {
246                     if ((name = (char *)malloc (strlen(result[index]->value)+strlen(line)+3)) == NULL) {
247                         for (index=0; result[index]; index++)
248                             free (result[index]);
249                         free (result);
250                         return NULL;
251                     }
252                     sprintf (name, "%s\r\n%s", result[index]->value, line);
253                     free (result[index]->value);
254                     result[index]->value = name;
255                     name = NULL;
256                 }
257             }
258         }
259
260     }
261
262     if ((res = (s_cgi *)malloc (sizeof (s_cgi))) == NULL)
263        return NULL;
264
265     res->vars = result;
266     res->cookies = NULL;
267     res->files = NULL;
268
269     return res;
270 }
271
272 /*  cgiReadVariables()
273  *
274  *  Read from stdin if no string is provided via CGI.  Variables that
275  *  doesn't have a value associated with it doesn't get stored.
276  */
277 s_cgi *cgiReadVariables ()
278 {
279     int length;
280     char *line = NULL;
281     int numargs;
282     char *cp, *ip, *esp, *sptr;
283     s_var **result;
284     int i, k, len;
285     char tmp[101];
286     s_cgi *res;
287
288     cp = getenv("CONTENT_TYPE");
289     cgiDebugOutput (2, "Content-Type: %s", cp);
290     if (cp && strstr(cp, "multipart/form-data") && strstr(cp, "boundary=")) {
291         cp = strstr(cp, "boundary=") + strlen ("boundary=") - 2;
292         *cp = *(cp+1) = '-';
293         return cgiReadMultipart (cp);
294     }
295
296     cp = getenv("REQUEST_METHOD");
297     cgiDebugOutput (2, "REQUEST_METHOD: %s", cp);
298     ip = getenv("CONTENT_LENGTH");
299
300     if (cp && !strcmp(cp, "POST")) {
301         if (ip) {
302             length = atoi(ip);
303             if (length <= 0)
304                 return NULL;
305             if ((line = (char *)malloc (length+2)) == NULL)
306                 return NULL;
307             fgets(line, length+1, stdin);
308         } else
309             return NULL;
310     } else if (cp && !strcmp(cp, "GET")) {
311         esp = getenv("QUERY_STRING");
312         if (esp && strlen(esp)) {
313             if ((line = (char *)malloc (strlen(esp)+2)) == NULL)
314                 return NULL;
315             sprintf (line, "%s", esp);
316         } else
317             return NULL;
318     } else {
319         length = 0;
320         printf ("(offline mode: enter name=value pairs on standard input)\n");
321         memset (tmp, 0, sizeof(tmp));
322         while((cp = fgets (tmp, 100, stdin)) != NULL) {
323             if (strlen(tmp)) {
324                 if (tmp[strlen(tmp)-1] == '\n')
325                     tmp[strlen(tmp)-1] = '&';
326                 if (length) {
327                     length += strlen(tmp);
328                     len = (length+1) * sizeof(char);
329                     if ((line = (char *)realloc (line, len)) == NULL)
330                         return NULL;
331                     strcat (line, tmp);
332                 } else {
333                     length = strlen(tmp);
334                     len = (length+1) * sizeof(char);
335                     if ((line = (char *)malloc (len)) == NULL)
336                         return NULL;
337                     memset (line, 0, len);
338                     strcpy (line, tmp);
339                 }
340             }
341             memset (tmp, 0, sizeof(tmp));
342         }
343         if (!line)
344             return NULL;
345         if (line[strlen(line)-1] == '&')
346             line[strlen(line)-1] = '\0';
347     }
348
349     /*
350      *  From now on all cgi variables are stored in the variable line
351      *  and look like  foo=bar&foobar=barfoo&foofoo=
352      */
353
354     cgiDebugOutput (1, "Received CGI input: %s", line);
355
356     for (cp=line; *cp; cp++)
357         if (*cp == '+')
358             *cp = ' ';
359
360     if (strlen(line)) {
361         for (numargs=1,cp=line; *cp; cp++)
362             if (*cp == '&' || *cp == ';' ) numargs++;
363     } else
364         numargs = 0;
365     cgiDebugOutput (1, "%d cgi variables found.", numargs);
366
367     len = (numargs+1) * sizeof(s_var *);
368     if ((result = (s_var **)malloc (len)) == NULL)
369         return NULL;
370     memset (result, 0, len);
371
372     cp = line;
373     i=0;
374     while (*cp) {
375         if ((ip = (char *)strchr(cp, '&')) != NULL) {
376             *ip = '\0';
377         } else if ((ip = (char *)strchr(cp, ';')) != NULL) {
378             *ip = '\0';
379         } else
380             ip = cp + strlen(cp);
381
382         if ((esp=(char *)strchr(cp, '=')) == NULL) {
383             cp = ++ip;
384             continue;
385         }
386
387         if (!strlen(esp)) {
388             cp = ++ip;
389             continue;
390         }
391
392         if (i<numargs) {
393
394             /* try to find out if there's already such a variable */
395             for (k=0; k<i && (strncmp (result[k]->name,cp, esp-cp) || !(strlen (result[k]->name) == esp-cp)); k++);
396
397             if (k == i) {       /* No such variable yet */
398                 if ((result[i] = (s_var *)malloc(sizeof(s_var))) == NULL)
399                     return NULL;
400                 if ((result[i]->name = (char *)malloc((esp-cp+1) * sizeof(char))) == NULL)
401                     return NULL;
402                 memset (result[i]->name, 0, esp-cp+1);
403                 strncpy(result[i]->name, cp, esp-cp);
404                 cp = ++esp;
405                 if ((result[i]->value = (char *)malloc((ip-esp+1) * sizeof(char))) == NULL)
406                     return NULL;
407                 memset (result[i]->value, 0, ip-esp+1);
408                 strncpy(result[i]->value, cp, ip-esp);
409                 result[i]->value = cgiDecodeString(result[i]->value);
410                 cgiDebugOutput (1, "%s: %s", result[i]->name, result[i]->value);
411                 i++;
412             } else {    /* There is already such a name, suppose a mutiple field */
413                 cp = ++esp;
414                 len = (strlen(result[k]->value)+(ip-esp)+2) * sizeof (char);
415                 if ((sptr = (char *)malloc(len)) == NULL)
416                     return NULL;
417                 memset (sptr, 0, len);
418                 sprintf (sptr, "%s\n", result[k]->value);
419                 strncat(sptr, cp, ip-esp);
420                 free(result[k]->value);
421                 result[k]->value = cgiDecodeString (sptr);
422             }
423         }
424         cp = ++ip;
425     }
426
427     if ((res = (s_cgi *)malloc (sizeof (s_cgi))) == NULL)
428         return NULL;
429
430     res->vars = result;
431     res->cookies = NULL;
432     res->files = NULL;
433
434     return res;
435 }
436
437 /*  cgiInit()
438  *
439  *  Read from stdin if no string is provided via CGI.  Variables that
440  *  doesn't have a value associated with it doesn't get stored.
441  */
442 s_cgi *cgiInit()
443 {
444     s_cgi *res;
445
446     res = cgiReadVariables ();
447     res->cookies = cgiReadCookies ();
448
449     if (!res->vars && !res->cookies && !res->files) {
450         free (res);
451         return NULL;
452     }
453
454     return res;
455 }
456
457 char *cgiGetValue (s_cgi *parms, const char *name)
458 {
459     int i;
460
461     if (!parms || !parms->vars)
462         return NULL;
463     for (i=0;parms->vars[i]; i++)
464         if (!strcmp(name,parms->vars[i]->name)) {
465             cgiDebugOutput (1, "%s found as %s", name, parms->vars[i]->value);
466             if (strlen(parms->vars[i]->value) > 0)
467                 return parms->vars[i]->value;
468             else
469                 return NULL;
470         }
471     cgiDebugOutput (1, "%s not found", name);
472     return NULL;
473 }
474
475 char **cgiGetVariables (s_cgi *parms)
476 {
477     int i;
478     char **res = NULL;
479     int len;
480
481     if (!parms || !parms->vars)
482         return NULL;
483     for (i=0;parms->vars[i]; i++);
484     len = sizeof (char *) * ++i;
485     if ((res = (char **)malloc (len)) == NULL)
486         return NULL;
487     memset (res, 0, len);
488     for (i=0;parms->vars[i]; i++) {
489         len = strlen (parms->vars[i]->name) +1;
490         if ((res[i] = (char *)malloc (len)) == NULL)
491             return NULL;
492         memset (res[i], 0, len);
493         strcpy (res[i], parms->vars[i]->name);
494     }
495     return res;
496 }
497
498 void cgiRedirect (const char *url)
499 {
500     if (url && strlen(url)) {
501         printf ("Content-type: text/html\r\nContent-length: %d\r\n", 77+(strlen(url)*2));
502         printf ("Status: 302 Temporal Relocation\r\n");
503         printf ("Location: %s\r\n\r\n", url);
504         printf ("<html>\n<body>\nThe page has been moved to <a href=\"%s\">%s</a>\n</body>\n</html>\n", url, url);
505     }
506 }
507
508 void cgiFreeList (char **list)
509 {
510     int i;
511
512     for (i=0; list[i] != NULL; i++)
513         free (list[i]);
514         free (list);
515 }
516
517 void cgiFree (s_cgi *parms)
518 {
519     int i;
520
521     if (!parms)
522         return;
523     if (parms->vars) {
524         for (i=0;parms->vars[i]; i++) {
525             if (parms->vars[i]->name)
526                 free (parms->vars[i]->name);
527             if (parms->vars[i]->value)
528                 free (parms->vars[i]->value);
529             free (parms->vars[i]);
530         }
531         free (parms->vars);
532     }
533     if (parms->cookies) {
534         if (parms->cookies[0]->version)
535             free (parms->cookies[0]->version);
536         for (i=0;parms->cookies[i]; i++) {
537             if (parms->cookies[i]->name)
538                 free (parms->cookies[i]->name);
539             if (parms->cookies[i]->value)
540                 free (parms->cookies[i]->value);
541             if (parms->cookies[i]->path)
542                 free (parms->cookies[i]->path);
543             if (parms->cookies[i]->domain)
544                 free (parms->cookies[i]->domain);
545             free (parms->cookies[i]);
546         }
547         free (parms->cookies);
548     }
549     free (parms);
550
551     if (cgiHeaderString) {
552         free (cgiHeaderString);
553         cgiHeaderString = NULL;
554     }
555     if (cgiType) {
556         free (cgiType);
557         cgiType = NULL;
558     }
559 }
560
561 /*
562  * Local variables:
563  *  c-indent-level: 4
564  *  c-basic-offset: 4
565  *  tab-width: 8
566  * End:
567  */