/* * CGI routines for luci * Copyright (C) 2008 Felix Fietkau <nbd@openwrt.org> * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ /* * Based on code from cgilib: * * cgi.c - Some simple routines for CGI programming * Copyright (c) 1996-9,2007,8 Martin Schulze <joey@infodrom.org> * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #define _GNU_SOURCE 1 #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <stdbool.h> #include <strings.h> #include <ctype.h> #include <lauxlib.h> #define BUFSIZE 128 static char * cgiGetLine (FILE *stream) { static char *line = NULL; static size_t size = 0; char buf[BUFSIZE]; char *cp; if (!line) { if ((line = (char *)malloc (BUFSIZE)) == NULL) return NULL; size = BUFSIZE; } line[0] = '\0'; while (!feof (stream)) { if ((cp = fgets (buf, sizeof (buf), stream)) == NULL) return NULL; if (strlen(line)+strlen(buf)+1 > size) { if ((cp = (char *)realloc (line, size + BUFSIZE)) == NULL) return line; size += BUFSIZE; line = cp; } strcat (line, buf); if (line[strlen(line)-1] == '\n') { line[strlen(line)-1] = '\0'; if (line[strlen(line)-1] == '\r') line[strlen(line)-1] = '\0'; return line; } } return NULL; } static const char * luci_getenv(lua_State *L, const char *name) { const char *ret; lua_getfield(L, lua_upvalueindex(2), name); ret = lua_tostring(L, -1); lua_pop(L, 1); return ret; } static void luci_setvar(lua_State *L, const char *name, const char *value, bool append) { /* Check if there is an existing value already */ lua_getfield(L, lua_upvalueindex(1), name); if (lua_isnil(L, -1)) { /* nope, we're safe - add a new one */ lua_pushstring(L, value); lua_setfield(L, lua_upvalueindex(1), name); } else if (lua_istable(L, -1) && append) { /* it's a table already, but appending is requested * take the last element and append the new string to it */ int tlast = lua_objlen(L, -1); lua_rawgeti(L, -1, tlast); lua_pushstring(L, value); lua_pushstring(L, "\n"); lua_concat(L, 3); lua_rawseti(L, -2, tlast); } else if (lua_istable(L, -1)) { /* it's a table, which means we already have two * or more entries, add the next one */ int tnext = lua_objlen(L, -1) + 1; /* next entry */ lua_pushstring(L, value); luaL_setn(L, -2, tnext); lua_rawseti(L, -2, tnext); } else if (lua_isstring(L, -1) && append) { /* append the new string to the existing variable */ lua_pushstring(L, value); lua_pushstring(L, "\n"); lua_concat(L, 3); lua_setfield(L, lua_upvalueindex(1), name); } else if (lua_isstring(L, -1)) { /* we're trying to add a variable that already has * a string value. convert the string value to a * table and add our new value to the table as well */ lua_createtable(L, 2, 0); lua_pushvalue(L, -2); /* copy of the initial string value */ lua_rawseti(L, -2, 1); lua_pushstring(L, value); lua_rawseti(L, -2, 2); lua_setfield(L, lua_upvalueindex(1), name); } else { luaL_error(L, "Invalid table entry type for index '%s'", name); } } char *cgiDecodeString (char *text) { char *cp, *xp; for (cp=text,xp=text; *cp; cp++) { if (*cp == '%') { if (strchr("0123456789ABCDEFabcdef", *(cp+1)) && strchr("0123456789ABCDEFabcdef", *(cp+2))) { if (islower(*(cp+1))) *(cp+1) = toupper(*(cp+1)); if (islower(*(cp+2))) *(cp+2) = toupper(*(cp+2)); *(xp) = (*(cp+1) >= 'A' ? *(cp+1) - 'A' + 10 : *(cp+1) - '0' ) * 16 + (*(cp+2) >= 'A' ? *(cp+2) - 'A' + 10 : *(cp+2) - '0'); xp++;cp+=2; } } else { *(xp++) = *cp; } } memset(xp, 0, cp-xp); return text; } #if 0 /* cgiReadFile() * * Read and save a file fro a multipart request */ #include <errno.h> char *cgiReadFile (FILE *stream, char *boundary) { char *crlfboundary, *buf; size_t boundarylen; int c; unsigned int pivot; char *cp; char template[]= "/tmp/cgilibXXXXXX"; FILE *tmpfile; int fd; boundarylen = strlen(boundary)+3; if ((crlfboundary = (char *)malloc (boundarylen)) == NULL) return NULL; sprintf (crlfboundary, "\r\n%s", boundary); if ((buf = (char *)malloc (boundarylen)) == NULL) { free (crlfboundary); return NULL; } memset (buf, 0, boundarylen); pivot = 0; if ((fd = mkstemp (template)) == -1) { free (crlfboundary); free (buf); return NULL; } if ((tmpfile = fdopen (fd, "w")) == NULL) { free (crlfboundary); free (buf); unlink (template); return NULL; } while (!feof (stream)) { c = fgetc (stream); if (c == 0) { if (strlen (buf)) { for (cp=buf; *cp; cp++) putc (*cp, tmpfile); memset (buf, 0, boundarylen); pivot = 0; } putc (c, tmpfile); continue; } if (strlen (buf)) { if (crlfboundary[pivot+1] == c) { buf[++pivot] = c; if (strlen (buf) == strlen (crlfboundary)) break; else continue; } else { for (cp=buf; *cp; cp++) putc (*cp, tmpfile); memset (buf, 0, boundarylen); pivot = 0; } } if (crlfboundary[0] == c) { buf[0] = c; } else { fputc (c, tmpfile); } } if (!feof (stream)) fgets (buf, boundarylen, stream); fclose (tmpfile); free (crlfboundary); free (buf); return strdup (template); } #endif /* * Decode multipart/form-data */ #define MULTIPART_DELTA 5 void luci_parse_multipart (lua_State *L, char *boundary) { char *line; char *cp, *xp; char *name = NULL, *type = NULL; char *fname = NULL; int header = 1; bool append = false; while ((line = cgiGetLine (stdin)) != NULL) { if (!strncmp (line, boundary, strlen(boundary))) { header = 1; if (name) free(name); if (type) free(type); name = NULL; type = NULL; append = false; } else if (header && !name && !strncasecmp (line, "Content-Disposition: form-data; ", 32)) { if ((cp = strstr (line, "name=\"")) == NULL) continue; cp += 6; if ((xp = strchr (cp, '\"')) == NULL) continue; name = malloc(xp-cp + 1); strncpy(name, cp, xp-cp); name[xp-cp] = 0; cgiDecodeString (name); if ((cp = strstr (line, "filename=\"")) == NULL) continue; cp += 10; if ((xp = strchr (cp, '\"')) == NULL) continue; fname = malloc(xp-cp + 1); strncpy(fname, cp, xp-cp); fname[xp-cp] = 0; cgiDecodeString (fname); } else if (header && !type && !strncasecmp (line, "Content-Type: ", 14)) { cp = line + 14; type = strdup (cp); } else if (header) { if (!strlen(line)) { header = 0; if (fname) { #if 0 header = 1; tmpfile = cgiReadFile (stdin, boundary); if (!tmpfile) { free (name); free (fname); if (type) free (type); name = fname = type = NULL; } cgiDebugOutput (2, "Wrote %s (%s) to file: %s", name, fname, tmpfile); if (!strlen (fname)) { cgiDebugOutput (3, "Found empty filename, removing"); unlink (tmpfile); free (tmpfile); free (name); free (fname); if (type) free (type); name = fname = type = NULL; } else { if ((file = (s_file *)malloc (sizeof (s_file))) == NULL) { cgiDebugOutput (3, "malloc failed, ignoring %s=%s", name, fname); unlink (tmpfile); free (tmpfile); free (name); free (fname); if (type) free (type); name = fname = type = NULL; continue; } file->name = name; file->type = type; file->tmpfile = tmpfile; if ((cp = rindex (fname, '/')) == NULL) file->filename = fname; else { file->filename = strdup (++cp); free (fname); } name = type = fname = NULL; if (!files) { if ((files = (s_file **)malloc(2*sizeof (s_file *))) == NULL) { cgiDebugOutput (3, "malloc failed, ignoring %s=%s", name, fname); unlink (tmpfile); free (tmpfile); free (name); name = NULL; if (type) { free (type); type = NULL; } free (file->filename); free (file); continue; } memset (files, 0, 2*sizeof (s_file *)); index = 0; } else { for (index=0; files[index]; index++); if ((tmpf = (s_file **)realloc(files, (index+2)*sizeof (s_file *))) == NULL) { cgiDebugOutput (3, "realloc failed, ignoring %s=%s", name, fname); unlink (tmpfile); free (tmpfile); free (name); if (type) free (type); free (file->filename); free (file); name = type = fname = NULL; continue; } files = tmpf; memset (files + index, 0, 2*sizeof (s_file *)); } files[index] = file; } #else free(fname); fname = NULL; #endif } } } else { if (!name) return; cgiDecodeString(line); luci_setvar(L, name, line, append); if (!append) /* beginning of variable contents */ append = true; } } } /* parse the request header and store variables * in the array supplied as function argument 1 on the stack */ int luci_parse_header (lua_State *L) { int length; char *line = NULL; int numargs; char *cp = NULL, *ip = NULL, *esp = NULL; const char *ct, *il; int i; if (!lua_istable(L, lua_upvalueindex(1))) luaL_error(L, "Invalid argument"); if (!lua_istable(L, lua_upvalueindex(2))) luaL_error(L, "Invalid argument"); ct = luci_getenv(L, "content_type"); if (ct) { ct = cp = strdup(ct); } if (cp && strstr(cp, "multipart/form-data") && strstr(cp, "boundary=")) { cp = strstr(cp, "boundary=") + strlen ("boundary=") - 2; *cp = *(cp+1) = '-'; luci_parse_multipart(L, cp); free((char *) ct); return 0; } free((char *) ct); ct = luci_getenv(L, "request_method"); il = luci_getenv(L, "content_length"); if (!ct) { fprintf(stderr, "no request method!\n"); return 0; } if (!strcmp(ct, "POST")) { if (il) { length = atoi(il); if (length <= 0) return 0; line = (char *)malloc (length+2); if (line) fgets(line, length+1, stdin); } } else if (!strcmp(ct, "GET")) { ct = luci_getenv(L, "query_string"); if (ct) esp = strdup(ct); if (esp && strlen(esp)) { line = (char *)malloc (strlen(esp)+2); if (line) strcpy (line, esp); } free(esp); } if (!line) return 0; /* * From now on all cgi variables are stored in the variable line * and look like foo=bar&foobar=barfoo&foofoo= */ for (cp=line; *cp; cp++) if (*cp == '+') *cp = ' '; if (strlen(line)) { for (numargs=1,cp=line; *cp; cp++) if (*cp == '&' || *cp == ';' ) numargs++; } else numargs = 0; cp = line; i=0; while (*cp) { char *name; char *value; if ((ip = (char *)strchr(cp, '&')) != NULL) { *ip = '\0'; } else if ((ip = (char *)strchr(cp, ';')) != NULL) { *ip = '\0'; } else ip = cp + strlen(cp); if ((esp=(char *)strchr(cp, '=')) == NULL) goto skip; if (!strlen(esp)) goto skip; if (i >= numargs) goto skip; esp[0] = 0; name = cp; cgiDecodeString (name); cp = ++esp; value = cp; cgiDecodeString (value); luci_setvar(L, name, value, false); skip: cp = ++ip; } free(line); return 0; }