Corrected pointer arithmetic
[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             cgiDebugOutput (2, "Found data: %s", line);
183             if (name) {
184
185                 /* try to find out if there's already such a variable */
186                 for (index=0; index<current && strcmp (result[index]->name,name); index++);
187
188                 if (index == current) {
189                     if (!result) {
190                         len = MULTIPART_DELTA * sizeof (s_var *);
191                         if ((result = (s_var **)malloc (len)) == NULL)
192                             return NULL;
193                         numresults = MULTIPART_DELTA;
194                         memset (result, 0, len);
195                         current = 0;
196                     } else {
197                         if (current+2 > numresults) {
198                             len = (numresults + MULTIPART_DELTA) * sizeof(s_var *);
199                             if ((tmp = (s_var **)realloc (result, len)) == NULL) {
200                                 for (index=0; result[index]; index++)
201                                     free (result[index]);
202                                 free (result);
203                                 free (name);
204                                 return NULL;
205                             }
206                             result = tmp;
207                             memset (result + numresults, 0, len - numresults*sizeof(s_var *));
208                             numresults += MULTIPART_DELTA;
209                         }
210                     }
211                     if ((result[current] = (s_var *)malloc(sizeof(s_var))) == NULL) {
212                         for (index=0; result[index]; index++)
213                             free (result[index]);
214                         free (result);
215                         free (name);
216                         return NULL;
217                     }
218                     current++;
219                     cgiDebugOutput (3, "Set #%d to %s=%s", index, name, line);
220                     result[index]->name = name; name = NULL;
221                     result[index]->value = strdup (line);
222                 } else {
223                     cgiDebugOutput (3, "Set #%d to %s=%s", index, name, line);
224                     free (name);
225                     if ((name = (char *)realloc (result[index]->value, strlen(result[index]->value)+strlen(line)+2)) != NULL) {
226                         strcat(name, "\n");
227                         strcat(name, line);
228                         result[index]->value = name;
229                         name = NULL;
230                     }
231                 }
232             } else {
233                 if (index > 0) {
234                     if ((name = (char *)malloc (strlen(result[index]->value)+strlen(line)+3)) == NULL) {
235                         for (index=0; result[index]; index++)
236                             free (result[index]);
237                         free (result);
238                         return NULL;
239                     }
240                     sprintf (name, "%s\r\n%s", result[index]->value, line);
241                     free (result[index]->value);
242                     result[index]->value = name;
243                     name = NULL;
244                 }
245             }
246         }
247
248     }
249
250     if ((res = (s_cgi *)malloc (sizeof (s_cgi))) == NULL)
251        return NULL;
252
253     res->vars = result;
254     res->cookies = NULL;
255     res->files = NULL;
256
257     return res;
258 }
259
260 /*  cgiReadVariables()
261  *
262  *  Read from stdin if no string is provided via CGI.  Variables that
263  *  doesn't have a value associated with it doesn't get stored.
264  */
265 s_cgi *cgiReadVariables ()
266 {
267     int length;
268     char *line = NULL;
269     int numargs;
270     char *cp, *ip, *esp, *sptr;
271     s_var **result;
272     int i, k, len;
273     char tmp[101];
274     s_cgi *res;
275
276     cp = getenv("CONTENT_TYPE");
277     cgiDebugOutput (2, "Content-Type: %s", cp);
278     if (cp && strstr(cp, "multipart/form-data") && strstr(cp, "boundary=")) {
279         cp = strstr(cp, "boundary=") + strlen ("boundary=") - 2;
280         *cp = *(cp+1) = '-';
281         return cgiReadMultipart (cp);
282     }
283
284     cp = getenv("REQUEST_METHOD");
285     cgiDebugOutput (2, "REQUEST_METHOD: %s", cp);
286     ip = getenv("CONTENT_LENGTH");
287
288     if (cp && !strcmp(cp, "POST")) {
289         if (ip) {
290             length = atoi(ip);
291             if (length <= 0)
292                 return NULL;
293             if ((line = (char *)malloc (length+2)) == NULL)
294                 return NULL;
295             fgets(line, length+1, stdin);
296         } else
297             return NULL;
298     } else if (cp && !strcmp(cp, "GET")) {
299         esp = getenv("QUERY_STRING");
300         if (esp && strlen(esp)) {
301             if ((line = (char *)malloc (strlen(esp)+2)) == NULL)
302                 return NULL;
303             sprintf (line, "%s", esp);
304         } else
305             return NULL;
306     } else {
307         length = 0;
308         printf ("(offline mode: enter name=value pairs on standard input)\n");
309         memset (tmp, 0, sizeof(tmp));
310         while((cp = fgets (tmp, 100, stdin)) != NULL) {
311             if (strlen(tmp)) {
312                 if (tmp[strlen(tmp)-1] == '\n')
313                     tmp[strlen(tmp)-1] = '&';
314                 if (length) {
315                     length += strlen(tmp);
316                     len = (length+1) * sizeof(char);
317                     if ((line = (char *)realloc (line, len)) == NULL)
318                         return NULL;
319                     strcat (line, tmp);
320                 } else {
321                     length = strlen(tmp);
322                     len = (length+1) * sizeof(char);
323                     if ((line = (char *)malloc (len)) == NULL)
324                         return NULL;
325                     memset (line, 0, len);
326                     strcpy (line, tmp);
327                 }
328             }
329             memset (tmp, 0, sizeof(tmp));
330         }
331         if (!line)
332             return NULL;
333         if (line[strlen(line)-1] == '&')
334             line[strlen(line)-1] = '\0';
335     }
336
337     /*
338      *  From now on all cgi variables are stored in the variable line
339      *  and look like  foo=bar&foobar=barfoo&foofoo=
340      */
341
342     cgiDebugOutput (1, "Received CGI input: %s", line);
343
344     for (cp=line; *cp; cp++)
345         if (*cp == '+')
346             *cp = ' ';
347
348     if (strlen(line)) {
349         for (numargs=1,cp=line; *cp; cp++)
350             if (*cp == '&' || *cp == ';' ) numargs++;
351     } else
352         numargs = 0;
353     cgiDebugOutput (1, "%d cgi variables found.", numargs);
354
355     len = (numargs+1) * sizeof(s_var *);
356     if ((result = (s_var **)malloc (len)) == NULL)
357         return NULL;
358     memset (result, 0, len);
359
360     cp = line;
361     i=0;
362     while (*cp) {
363         if ((ip = (char *)strchr(cp, '&')) != NULL) {
364             *ip = '\0';
365         } else if ((ip = (char *)strchr(cp, ';')) != NULL) {
366             *ip = '\0';
367         } else
368             ip = cp + strlen(cp);
369
370         if ((esp=(char *)strchr(cp, '=')) == NULL) {
371             cp = ++ip;
372             continue;
373         }
374
375         if (!strlen(esp)) {
376             cp = ++ip;
377             continue;
378         }
379
380         if (i<numargs) {
381
382             /* try to find out if there's already such a variable */
383             for (k=0; k<i && (strncmp (result[k]->name,cp, esp-cp) || !(strlen (result[k]->name) == esp-cp)); k++);
384
385             if (k == i) {       /* No such variable yet */
386                 if ((result[i] = (s_var *)malloc(sizeof(s_var))) == NULL)
387                     return NULL;
388                 if ((result[i]->name = (char *)malloc((esp-cp+1) * sizeof(char))) == NULL)
389                     return NULL;
390                 memset (result[i]->name, 0, esp-cp+1);
391                 strncpy(result[i]->name, cp, esp-cp);
392                 cp = ++esp;
393                 if ((result[i]->value = (char *)malloc((ip-esp+1) * sizeof(char))) == NULL)
394                     return NULL;
395                 memset (result[i]->value, 0, ip-esp+1);
396                 strncpy(result[i]->value, cp, ip-esp);
397                 result[i]->value = cgiDecodeString(result[i]->value);
398                 cgiDebugOutput (1, "%s: %s", result[i]->name, result[i]->value);
399                 i++;
400             } else {    /* There is already such a name, suppose a mutiple field */
401                 cp = ++esp;
402                 len = (strlen(result[k]->value)+(ip-esp)+2) * sizeof (char);
403                 if ((sptr = (char *)malloc(len)) == NULL)
404                     return NULL;
405                 memset (sptr, 0, len);
406                 sprintf (sptr, "%s\n", result[k]->value);
407                 strncat(sptr, cp, ip-esp);
408                 free(result[k]->value);
409                 result[k]->value = cgiDecodeString (sptr);
410             }
411         }
412         cp = ++ip;
413     }
414
415     if ((res = (s_cgi *)malloc (sizeof (s_cgi))) == NULL)
416         return NULL;
417
418     res->vars = result;
419     res->cookies = NULL;
420     res->files = NULL;
421
422     return res;
423 }
424
425 /*  cgiInit()
426  *
427  *  Read from stdin if no string is provided via CGI.  Variables that
428  *  doesn't have a value associated with it doesn't get stored.
429  */
430 s_cgi *cgiInit()
431 {
432     s_cgi *res;
433
434     res = cgiReadVariables ();
435     res->cookies = cgiReadCookies ();
436
437     if (!res->vars && !res->cookies && !res->files) {
438         free (res);
439         return NULL;
440     }
441
442     return res;
443 }
444
445 char *cgiGetValue (s_cgi *parms, const char *name)
446 {
447     int i;
448
449     if (!parms || !parms->vars)
450         return NULL;
451     for (i=0;parms->vars[i]; i++)
452         if (!strcmp(name,parms->vars[i]->name)) {
453             cgiDebugOutput (1, "%s found as %s", name, parms->vars[i]->value);
454             if (strlen(parms->vars[i]->value) > 0)
455                 return parms->vars[i]->value;
456             else
457                 return NULL;
458         }
459     cgiDebugOutput (1, "%s not found", name);
460     return NULL;
461 }
462
463 char **cgiGetVariables (s_cgi *parms)
464 {
465     int i;
466     char **res = NULL;
467     int len;
468
469     if (!parms || !parms->vars)
470         return NULL;
471     for (i=0;parms->vars[i]; i++);
472     len = sizeof (char *) * ++i;
473     if ((res = (char **)malloc (len)) == NULL)
474         return NULL;
475     memset (res, 0, len);
476     for (i=0;parms->vars[i]; i++) {
477         len = strlen (parms->vars[i]->name) +1;
478         if ((res[i] = (char *)malloc (len)) == NULL)
479             return NULL;
480         memset (res[i], 0, len);
481         strcpy (res[i], parms->vars[i]->name);
482     }
483     return res;
484 }
485
486 void cgiRedirect (const char *url)
487 {
488     if (url && strlen(url)) {
489         printf ("Content-type: text/html\r\nContent-length: %d\r\n", 77+(strlen(url)*2));
490         printf ("Status: 302 Temporal Relocation\r\n");
491         printf ("Location: %s\r\n\r\n", url);
492         printf ("<html>\n<body>\nThe page has been moved to <a href=\"%s\">%s</a>\n</body>\n</html>\n", url, url);
493     }
494 }
495
496 void cgiFreeList (char **list)
497 {
498     int i;
499
500     for (i=0; list[i] != NULL; i++)
501         free (list[i]);
502         free (list);
503 }
504
505 void cgiFree (s_cgi *parms)
506 {
507     int i;
508
509     if (!parms)
510         return;
511     if (parms->vars) {
512         for (i=0;parms->vars[i]; i++) {
513             if (parms->vars[i]->name)
514                 free (parms->vars[i]->name);
515             if (parms->vars[i]->value)
516                 free (parms->vars[i]->value);
517             free (parms->vars[i]);
518         }
519         free (parms->vars);
520     }
521     if (parms->cookies) {
522         if (parms->cookies[0]->version)
523             free (parms->cookies[0]->version);
524         for (i=0;parms->cookies[i]; i++) {
525             if (parms->cookies[i]->name)
526                 free (parms->cookies[i]->name);
527             if (parms->cookies[i]->value)
528                 free (parms->cookies[i]->value);
529             if (parms->cookies[i]->path)
530                 free (parms->cookies[i]->path);
531             if (parms->cookies[i]->domain)
532                 free (parms->cookies[i]->domain);
533             free (parms->cookies[i]);
534         }
535         free (parms->cookies);
536     }
537     free (parms);
538
539     if (cgiHeaderString) {
540         free (cgiHeaderString);
541         cgiHeaderString = NULL;
542     }
543     if (cgiType) {
544         free (cgiType);
545         cgiType = NULL;
546     }
547 }
548
549 /*
550  * Local variables:
551  *  c-indent-level: 4
552  *  c-basic-offset: 4
553  *  tab-width: 8
554  * End:
555  */