Squashed commit of the following:
[project/luci.git] / libs / sgi-webuci / src / cgi.c
1 /*
2  * CGI routines for luci
3  * Copyright (C) 2008 Felix Fietkau <nbd@openwrt.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 version 2
7  * as published by the Free Software Foundation.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  */
14
15 /* 
16  * Based on code from cgilib:
17  * 
18  *   cgi.c - Some simple routines for CGI programming
19  *   Copyright (c) 1996-9,2007,8  Martin Schulze <joey@infodrom.org>
20  *
21  *   This program is free software; you can redistribute it and/or modify
22  *   it under the terms of the GNU General Public License as published by
23  *   the Free Software Foundation; either version 2 of the License, or
24  *   (at your option) any later version.
25  *
26  *   This program is distributed in the hope that it will be useful,
27  *   but WITHOUT ANY WARRANTY; without even the implied warranty of
28  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
29  *   GNU General Public License for more details.
30  *
31  *   You should have received a copy of the GNU General Public License
32  *   along with this program; if not, write to the Free Software Foundation
33  *   Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
34  */
35
36 #define _GNU_SOURCE 1
37
38 #include <stdio.h>
39 #include <stdlib.h>
40 #include <unistd.h>
41 #include <string.h>
42 #include <stdbool.h>
43 #include <strings.h>
44 #include <ctype.h>
45 #include <lauxlib.h>
46
47 #define BUFSIZE 128
48
49 static char *
50 cgiGetLine (FILE *stream)
51 {
52         static char *line = NULL;
53         static size_t size = 0;
54         char buf[BUFSIZE];
55         char *cp;
56
57         if (!line) {
58                 if ((line = (char *)malloc (BUFSIZE)) == NULL)
59                         return NULL;
60                 size = BUFSIZE;
61         }
62         line[0] = '\0';
63
64         while (!feof (stream)) {
65                 if ((cp = fgets (buf, sizeof (buf), stream)) == NULL)
66                         return NULL;
67
68                 if (strlen(line)+strlen(buf)+1 > size) {
69                         if ((cp = (char *)realloc (line, size + BUFSIZE)) == NULL)
70                                 return line;
71                         size += BUFSIZE;
72                         line = cp;
73                 }
74
75                 strcat (line, buf);
76                 if (line[strlen(line)-1] == '\n') {
77                         line[strlen(line)-1] = '\0';
78                         if (line[strlen(line)-1] == '\r')
79                                 line[strlen(line)-1] = '\0';
80                         return line;
81                 }
82         }
83
84         return NULL;
85 }
86
87
88 static const char *
89 luci_getenv(lua_State *L, const char *name)
90 {
91         const char *ret;
92
93         lua_getfield(L, lua_upvalueindex(2), name);
94         ret = lua_tostring(L, -1);
95         lua_pop(L, 1);
96         return ret;
97 }
98
99 static void
100 luci_setvar(lua_State *L, const char *name, const char *value, bool append)
101 {
102         /* Check if there is an existing value already */
103         lua_getfield(L, lua_upvalueindex(1), name);
104         if (lua_isnil(L, -1)) {
105                 /* nope, we're safe - add a new one */
106                 lua_pushstring(L, value);
107                 lua_setfield(L, lua_upvalueindex(1), name);
108         } else if (lua_istable(L, -1) && append) {
109                 /* it's a table already, but appending is requested
110                  * take the last element and append the new string to it */
111                 int tlast = lua_objlen(L, -1);
112                 lua_rawgeti(L, -1, tlast);
113                 lua_pushstring(L, value);
114                 lua_pushstring(L, "\n");
115                 lua_concat(L, 3);
116                 lua_rawseti(L, -2, tlast);
117         } else if (lua_istable(L, -1)) {
118                 /* it's a table, which means we already have two
119                  * or more entries, add the next one */
120
121                 int tnext = lua_objlen(L, -1) + 1; /* next entry */
122
123                 lua_pushstring(L, value);
124                 luaL_setn(L, -2, tnext);
125                 lua_rawseti(L, -2, tnext);
126         } else if (lua_isstring(L, -1) && append) {
127                 /* append the new string to the existing variable */
128                 lua_pushstring(L, value);
129                 lua_pushstring(L, "\n");
130                 lua_concat(L, 3);
131                 lua_setfield(L, lua_upvalueindex(1), name);
132         } else if (lua_isstring(L, -1)) {
133                 /* we're trying to add a variable that already has
134                  * a string value. convert the string value to a
135                  * table and add our new value to the table as well
136                  */
137                 lua_createtable(L, 2, 0);
138                 lua_pushvalue(L, -2); /* copy of the initial string value */
139                 lua_rawseti(L, -2, 1);
140
141                 lua_pushstring(L, value);
142                 lua_rawseti(L, -2, 2);
143                 lua_setfield(L, lua_upvalueindex(1), name);
144         } else {
145                 luaL_error(L, "Invalid table entry type for index '%s'", name);
146         }
147 }
148
149 char *cgiDecodeString (char *text)
150 {
151         char *cp, *xp;
152
153         for (cp=text,xp=text; *cp; cp++) {
154                 if (*cp == '%') {
155                         if (strchr("0123456789ABCDEFabcdef", *(cp+1))
156                                 && strchr("0123456789ABCDEFabcdef", *(cp+2))) {
157                                 if (islower(*(cp+1)))
158                                         *(cp+1) = toupper(*(cp+1));
159                                 if (islower(*(cp+2)))
160                                         *(cp+2) = toupper(*(cp+2));
161                                 *(xp) = (*(cp+1) >= 'A' ? *(cp+1) - 'A' + 10 : *(cp+1) - '0' ) * 16
162                                         + (*(cp+2) >= 'A' ? *(cp+2) - 'A' + 10 : *(cp+2) - '0');
163                                 xp++;cp+=2;
164                         }
165                 } else {
166                         *(xp++) = *cp;
167                 }
168         }
169         memset(xp, 0, cp-xp);
170         return text;
171 }
172
173 #if 0
174 /* cgiReadFile()
175  *
176  * Read and save a file fro a multipart request
177  */
178 #include <errno.h>
179 char *cgiReadFile (FILE *stream, char *boundary)
180 {
181         char *crlfboundary, *buf;
182         size_t boundarylen;
183         int c;
184         unsigned int pivot;
185         char *cp;
186         char template[]= "/tmp/cgilibXXXXXX";
187         FILE *tmpfile;
188         int fd;
189
190         boundarylen = strlen(boundary)+3;
191         if ((crlfboundary = (char *)malloc (boundarylen)) == NULL)
192                 return NULL;
193         sprintf (crlfboundary, "\r\n%s", boundary);
194
195         if ((buf = (char *)malloc (boundarylen)) == NULL) {
196                 free (crlfboundary);
197                 return NULL;
198         }
199         memset (buf, 0, boundarylen);
200         pivot = 0;
201
202         if ((fd = mkstemp (template)) == -1) {
203                 free (crlfboundary);
204                 free (buf);
205                 return NULL;
206         }
207
208         if ((tmpfile = fdopen (fd, "w")) == NULL) {
209                 free (crlfboundary);
210                 free (buf);
211                 unlink (template);
212                 return NULL;
213         }
214         
215         while (!feof (stream)) {
216                 c = fgetc (stream);
217
218                 if (c == 0) {
219                         if (strlen (buf)) {
220                                 for (cp=buf; *cp; cp++)
221                                         putc (*cp, tmpfile);
222                                 memset (buf, 0, boundarylen);
223                                 pivot = 0;
224                         }
225                         putc (c, tmpfile);
226                         continue;
227                 }
228
229                 if (strlen (buf)) {
230                         if (crlfboundary[pivot+1] == c) {
231                                 buf[++pivot] = c;
232
233                                 if (strlen (buf) == strlen (crlfboundary))
234                                         break;
235                                 else
236                                         continue;
237                         } else {
238                                 for (cp=buf; *cp; cp++)
239                                         putc (*cp, tmpfile);
240                                 memset (buf, 0, boundarylen);
241                                 pivot = 0;
242                         }
243                 }
244
245                 if (crlfboundary[0] == c) {
246                         buf[0] = c;
247                 } else {
248                         fputc (c, tmpfile);
249                 }
250         }
251
252         if (!feof (stream))
253                 fgets (buf, boundarylen, stream);
254
255         fclose (tmpfile);
256
257         free (crlfboundary);
258         free (buf);
259
260         return strdup (template);
261 }
262 #endif
263
264 /*
265  * Decode multipart/form-data
266  */
267 #define MULTIPART_DELTA 5
268 void luci_parse_multipart (lua_State *L, char *boundary)
269 {
270         char *line;
271         char *cp, *xp;
272         char *name = NULL, *type = NULL;
273         char *fname = NULL;
274         int header = 1;
275         bool append = false;
276
277         while ((line = cgiGetLine (stdin)) != NULL) {
278                 if (!strncmp (line, boundary, strlen(boundary))) {
279                         header = 1;
280                         if (name)
281                                 free(name);
282                         if (type)
283                                 free(type);
284                         name = NULL;
285                         type = NULL;
286                         append = false;
287                 } else if (header && !name && !strncasecmp (line, "Content-Disposition: form-data; ", 32)) {
288                         if ((cp = strstr (line, "name=\"")) == NULL)
289                                 continue;
290                         cp += 6;
291                         if ((xp = strchr (cp, '\"')) == NULL)
292                                 continue;
293                         name = malloc(xp-cp + 1);
294                         strncpy(name, cp, xp-cp);
295                         name[xp-cp] = 0;
296                         cgiDecodeString (name);
297
298                         if ((cp = strstr (line, "filename=\"")) == NULL)
299                                 continue;
300                         cp += 10;
301                         if ((xp = strchr (cp, '\"')) == NULL)
302                                 continue;
303                         fname = malloc(xp-cp + 1);
304                         strncpy(fname, cp, xp-cp);
305                         fname[xp-cp] = 0;
306                         cgiDecodeString (fname);
307                 } else if (header && !type && !strncasecmp (line, "Content-Type: ", 14)) {
308                         cp = line + 14;
309                         type = strdup (cp);
310                 } else if (header) {
311                         if (!strlen(line)) {
312                                 header = 0;
313
314                                 if (fname) {
315 #if 0
316                                         header = 1;
317                                         tmpfile = cgiReadFile (stdin, boundary);
318
319                                         if (!tmpfile) {
320                                                 free (name);
321                                                 free (fname);
322                                                 if (type)
323                                                         free (type);
324                                                 name = fname = type = NULL;
325                                         }
326
327                                         cgiDebugOutput (2, "Wrote %s (%s) to file: %s", name, fname, tmpfile);
328
329                                         if (!strlen (fname)) {
330                                                 cgiDebugOutput (3, "Found empty filename, removing");
331                                                 unlink (tmpfile);
332                                                 free (tmpfile);
333                                                 free (name);
334                                                 free (fname);
335                                                 if (type)
336                                                         free (type);
337                                                 name = fname = type = NULL;
338                                         } else {
339                                                 if ((file = (s_file *)malloc (sizeof (s_file))) == NULL) {
340                                                         cgiDebugOutput (3, "malloc failed, ignoring %s=%s", name, fname);
341                                                         unlink (tmpfile);
342                                                         free (tmpfile);
343                                                         free (name);
344                                                         free (fname);
345                                                         if (type)
346                                                                 free (type);
347                                                         name = fname = type = NULL;
348                                                         continue;
349                                                 }
350
351                                                 file->name = name;
352                                                 file->type = type;
353                                                 file->tmpfile = tmpfile;
354                                                 if ((cp = rindex (fname, '/')) == NULL)
355                                                         file->filename = fname;
356                                                 else {
357                                                         file->filename = strdup (++cp);
358                                                         free (fname);
359                                                 }
360                                                 name = type = fname = NULL;
361
362                                                 if (!files) {
363                                                         if ((files = (s_file **)malloc(2*sizeof (s_file *))) == NULL) {
364                                                                 cgiDebugOutput (3, "malloc failed, ignoring %s=%s", name, fname);
365                                                                 unlink (tmpfile);
366                                                                 free (tmpfile);
367                                                                 free (name);
368                                                                 name = NULL;
369                                                                 if (type) {
370                                                                         free (type);
371                                                                         type = NULL;
372                                                                 }
373                                                                 free (file->filename);
374                                                                 free (file);
375                                                                 continue;
376                                                         }
377                                                         memset (files, 0, 2*sizeof (s_file *));
378                                                         index = 0;
379                                                 } else {
380                                                         for (index=0; files[index]; index++);
381                                                         if ((tmpf = (s_file **)realloc(files, (index+2)*sizeof (s_file *))) == NULL) {
382                                                                 cgiDebugOutput (3, "realloc failed, ignoring %s=%s", name, fname);
383                                                                 unlink (tmpfile);
384                                                                 free (tmpfile);
385                                                                 free (name);
386                                                                 if (type)
387                                                                         free (type);
388                                                                 free (file->filename);
389                                                                 free (file);
390                                                                 name = type = fname = NULL;
391                                                                 continue;
392                                                         }
393                                                         files = tmpf;
394                                                         memset (files + index, 0, 2*sizeof (s_file *));
395                                                 }
396                                                 files[index] = file;
397                                         }
398 #else
399                                         free(fname);
400                                         fname = NULL;
401 #endif
402                                 }
403                         }
404                 } else {
405                         if (!name)
406                                 return;
407
408                         cgiDecodeString(line);
409                         luci_setvar(L, name, line, append);
410                         if (!append) /* beginning of variable contents */
411                                 append = true;
412                 }
413         }
414 }
415
416 /* parse the request header and store variables
417  * in the array supplied as function argument 1 on the stack
418  */
419 int luci_parse_header (lua_State *L)
420 {
421         int length;
422         char *line = NULL;
423         int numargs;
424         char *cp = NULL, *ip = NULL, *esp = NULL;
425         const char *ct, *il;
426         int i;
427
428         if (!lua_istable(L, lua_upvalueindex(1)))
429                 luaL_error(L, "Invalid argument");
430
431         if (!lua_istable(L, lua_upvalueindex(2)))
432                 luaL_error(L, "Invalid argument");
433
434         ct = luci_getenv(L, "content_type");
435         if (ct) {
436                 ct = cp = strdup(ct);
437         }
438         if (cp && strstr(cp, "multipart/form-data") && strstr(cp, "boundary=")) {
439                 cp = strstr(cp, "boundary=") + strlen ("boundary=") - 2;
440                 *cp = *(cp+1) = '-';
441                 luci_parse_multipart(L, cp);
442                 free((char *) ct);
443                 return 0;
444         }
445         free((char *) ct);
446
447         ct = luci_getenv(L, "request_method");
448         il = luci_getenv(L, "content_length");
449
450         if (!ct) {
451                 fprintf(stderr, "no request method!\n");
452                 return 0;
453         }
454
455         if (!strcmp(ct, "POST")) {
456                 if (il) {
457                         length = atoi(il);
458                         if (length <= 0)
459                                 return 0;
460                         line = (char *)malloc (length+2);
461                         if (line)
462                                 fgets(line, length+1, stdin);
463                 }
464         } else if (!strcmp(ct, "GET")) {
465                 ct = luci_getenv(L, "query_string");
466                 if (ct)
467                         esp = strdup(ct);
468                 if (esp && strlen(esp)) {
469                         line = (char *)malloc (strlen(esp)+2);
470                         if (line)
471                                 strcpy (line, esp);
472                 }
473                 free(esp);
474         }
475
476         if (!line)
477                 return 0;
478
479         /*
480          *  From now on all cgi variables are stored in the variable line
481          *  and look like  foo=bar&foobar=barfoo&foofoo=
482          */
483         for (cp=line; *cp; cp++)
484                 if (*cp == '+')
485                         *cp = ' ';
486
487         if (strlen(line)) {
488                 for (numargs=1,cp=line; *cp; cp++)
489                         if (*cp == '&' || *cp == ';' ) numargs++;
490         } else
491                 numargs = 0;
492
493         cp = line;
494         i=0;
495         while (*cp) {
496                 char *name;
497                 char *value;
498
499                 if ((ip = (char *)strchr(cp, '&')) != NULL) {
500                         *ip = '\0';
501                 } else if ((ip = (char *)strchr(cp, ';')) != NULL) {
502                         *ip = '\0';
503                 } else
504                         ip = cp + strlen(cp);
505
506                 if ((esp=(char *)strchr(cp, '=')) == NULL)
507                         goto skip;
508
509                 if (!strlen(esp))
510                         goto skip;
511
512                 if (i >= numargs)
513                         goto skip;
514
515                 esp[0] = 0;
516                 name = cp;
517                 cgiDecodeString (name);
518
519                 cp = ++esp;
520                 value = cp;
521                 cgiDecodeString (value);
522
523                 luci_setvar(L, name, value, false);
524 skip:
525                 cp = ++ip;
526         }
527         free(line);
528         return 0;
529 }
530