531 lines
12 KiB
C
531 lines
12 KiB
C
/*
|
|
* 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;
|
|
}
|
|
|