#ifndef lint
static char RCSID[] = "$Id: cgi-postin.c,v 1.19 1997/01/05 06:35:09 chip Exp $ ";
#endif

/*
 * cgi-postin - Process the contents of a CGI ACTION=POST form.
 *
 * Chip Rosenthal
 * Unicom Systems Development
 * <chip@unicom.com>
 *
 * Edit at tabstops=4.
 *
 * Usage: cgi-postin [-pTt] [-v prefix]
 *
 * Thanks to:
 * - David DeSimone <fox@mikey.convex.com> for the CONTENT_LENGTH fix.
 * - Paul E. Hoffman <phoffman@proper.com> for the perl assistance.
 * - Stephen C. Trier <trier@odin.INS.CWRU.Edu> for the Tcl support.
 */

#define USAGE "usage: %s [-pTt] [-v prefix]"


/*
 * Porting definitions --  ADJUST THESE DEFINITIONS FOR YOUR SYSTEM
 */
#define HAVE_STDLIB_H		/* have ANSI-complient <stdlib.h> header	*/
#define HAVE_UNISTD_H		/* have POSIX-complient <unistd.h> header	*/
#define HAVE_STDARG_H		/* have ANSI-compliant <stdarg.h> facility	*/
#define HAVE_STRERROR		/* have strerror() function					*/

/*
 * This prefix is added to the front of variable names, with a "_"
 * seperating the prefix and name, or a NULL value to suppress the
 * prefix by default.  The prefix can be changed with the -v option.
*/
#define DFLT_PREFIX NULL

/*
 * I've been told that (particularly on slow links) if the client
 * aborts the transfer prior to completion, the CGI script is
 * subject to hang.  The TIMEOUT value, if defined, will abort
 * processing when this happens.
 */
#define TIMEOUT		(10*60) /* seconds */


#include <stdio.h>
#ifdef HAVE_STDLIB_H
#	include <stdlib.h>		/* for malloc(), getenv()	*/
#endif
#ifdef HAVE_UNISTD_H
#	include <unistd.h>		/* for STDIN_FILENO			*/
#endif
#include <signal.h>
#include <errno.h>			/* for errno				*/
#include <string.h>			/* for strerror()			*/
#include <ctype.h>
#ifdef HAVE_STDARG_H
#	include <stdarg.h>
#else
#	include <varargs.h>
#endif

#ifndef STDIN_FILENO
#	define STDIN_FILENO 0
#endif

#ifndef HAVE_STDLIB_H
	extern char *malloc(), *getenv();
#endif

#ifndef __STDC__
	extern int errno;
#endif

#ifndef HAVE_STRERROR
#	ifdef strerror
#		undef strerror
#	endif
	extern char *sys_errlist[];
#	define strerror(n)	sys_errlist[n]
#endif

#define TRUE		1
#define FALSE		0

/*
 * globals
 */
char *Progname;
char *Var_prefix;		/* "Prefix_" to prepend to variable names.	*/
int Terse_diagnostics;	/* If FALSE, errors printed as an HTTP doc.	*/

/*
 * Map 7-bit ASCII character [0-9a-fA-F] to its hex value.
 */
char Hexmap[0x80] = {
	 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
	 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
	 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
	0x0,0x1,0x2,0x3,0x4,0x5,0x6,0x7,0x8,0x9, -1, -1, -1, -1, -1, -1,
	 -1,0xA,0xB,0xC,0xD,0xE,0xF, -1, -1, -1, -1, -1, -1, -1, -1, -1,
	 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
	 -1,0xa,0xb,0xc,0xd,0xe,0xf, -1, -1, -1, -1, -1, -1, -1, -1, -1,
	 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
};
#define HexVal(c)		(Hexmap[(c) & 0x7F])

/*
 * local procedures
 */
#ifdef HAVE_STDARG_H
void die(int err, char *fmt, ...);
#else
void die();
#endif
void alarm_catcher();
char *unescape();
void emit_assignment_sh();
void emit_assignment_perl();
void emit_assignment_tcl();


int main(argc, argv)
int argc;
char *argv[];
{
	int len, n, i;
	register char *buf;
	char *beg_name, *beg_value, *s;
	void (*emit_assignment)();
	extern int optind;
	extern char *optarg;

	/*
	 * Initialize.
	 */
	Progname = argv[0];
	Var_prefix = NULL;
	Terse_diagnostics = FALSE;
	emit_assignment = emit_assignment_sh;

	/*
	 * Crack the command line.
	 */
	while ((i = getopt(argc, argv, "ptTv:")) != EOF) {
		switch (i) {
		case 'p':
			emit_assignment = emit_assignment_perl;
			break;
		case 'T':
			emit_assignment = emit_assignment_tcl;
			break;
		case 't':
			Terse_diagnostics = TRUE;
			break;
		case 'v':
			if (optarg[0] == '\0' || strcmp(optarg, "-") == 0)
				Var_prefix = NULL;
			else
				Var_prefix = optarg;
			break;
		default:
			die(0, USAGE, Progname);
		}
	}
	if (argc-optind != 0)
		die(0, USAGE, argv[0]);

	/* 
	 * Install timeout trap.
	 */
#ifdef TIMEOUT
	signal(SIGALRM, alarm_catcher);
	alarm(TIMEOUT);
#endif

	/*
	 * Sanity check of the CGI method.
	 */
	if ((s = getenv("REQUEST_METHOD")) == NULL)
		die(0, "cannot retrieve CGI REQUEST_METHOD");
	if (strcmp(s, "POST") != 0 && strcmp(s, "post") != 0)
		die(0, "expected REQUEST_METHOD of \"POST\", got \"%s\"", s);

	/*
	 * The server should create an environment parameter with the data size.
	 */
	if ((buf = getenv("CONTENT_LENGTH")) == NULL)
		die(0, "cannot retrieve CONTENT_LENGTH");
	len = atoi(buf);

	/*
	 * Create space to hold the raw data and load it in.
	 */
	if ((buf = (char *)malloc(len+1)) == NULL)
		die(0, "cannot allocate space for buffer");
	for (n = 0 ; n < len ; n += i) {
		if ((i = read(STDIN_FILENO, buf+n, len-n)) <= 0)
			die(errno, "error reading form data");
	}
	if (n != len)
		die(0, "error reading form data [expected %d chars, got %d]", len, n);
	buf[len] = '\0';

	/*
	 * Perform any format-dependant initializations.
	 */
	emit_assignment((char *)NULL, "initialize");

	/*
	 * Split up the buffer into (name,value) pairs.
	 */
	while (buf != NULL && *buf != '\0') {

		/*
		 * name=value&name=value...
		 * ^
		 */
		if (!isalpha(*buf) && *buf != '_')
			die(0, "unsafe char in field name");
		beg_name = buf++;

		/*
		 * name=value&name=value...
		 *  ^
		 */
		while (isalnum(*buf) || *buf == '_')
			++buf;

		/*
		 * name=value&name=value...
		 *     ^
		 */
		if (*buf != '=')
			die(0, "unsafe char in field name");
		*buf++ = '\0';

		/*
		 * name=value&name=value...
		 *      ^
		 */
		beg_value = buf;
		if ((buf = strchr(buf, '&')) != NULL)
			*buf++ = '\0';

		/*
		 * name=value&name=value...
		 *            ^
		 */
		emit_assignment(beg_name, unescape(beg_value));

	}

	emit_assignment((char *)NULL, "finish");
	exit(0);
	/*NOTREACHED*/
}


/*
 * Print an error message (with optional errno info) and terminate.
 * If "Terse_diagnostics" is TRUE print a conventional Unix diagnostic.
 * If FALSE emit an HTTP text/plain document.
 */
#ifdef HAVE_STDARG_H
void die(int err, char *fmt, ...)
#else
/*VARARGS*/
void die(va_alist)
va_dcl
#endif
{
	va_list ap;
#ifdef HAVE_STDARG_H
	va_start(ap, fmt);
#else
	int err;
	char *fmt;
	va_start(ap);
	err = va_arg(ap, int);
	fmt = va_arg(ap, char *);
#endif

	if (Terse_diagnostics) {
		fprintf(stderr, "%s: ", Progname);
	} else {
		fputs("\
Content-type: text/plain\n\
\n\
Error encountered while processing form data:\n\
\n\
\t", stderr);
	}
	vfprintf(stderr, fmt, ap);
	if (err > 0)
		fprintf(stderr, " [%s]", strerror(err));
	putc('\n', stderr);

	va_end(ap);
	exit(1);
	/*NOTREACHED*/
}


/*ARGSUSED*/
void alarm_catcher(signum)
int signum;
{
	die(0, "timeout waiting for POST data from client");
}


/*
 * Undo HTTP data conversions.
 */
char *unescape(buf)
char *buf;
{
	register char *src, *dest;

	src = dest = buf;
	while (*src != '\0') {

		switch (*src) {

		case '+':
			*dest++ = ' ';
			++src;
			break;

		case '%':
			if (HexVal(src[1]) < 0 || HexVal(src[2]) < 0)
				die(0, "malformed %%NN sequence");
			*dest++ = ((HexVal(src[1]) << 4) | HexVal(src[2]));
			src += 3;
			break;

		default:
			*dest++ = *src++;
			break;

		}

	}

	*dest = '\0';
	return buf;
}


/*
 * Output a sh assignment in format:  name='value'
 */
void emit_assignment_sh(name, value)
char *name, *value;
{
	register char *s;

	/* no special initialize or finish action required */
	if (name == NULL)
		return;

	if (Var_prefix == NULL) {
		/* prevent hacker from resetting "PATH" or "IFS" */
		if (getenv(name) != NULL)
			die(0, "security violation - attempt to override \"%s\" setting",
				name);
		printf("%s='", name);
	} else {
		printf("%s_%s='", Var_prefix, name);
	}

	for (s = value ; *s != '\0' ; ++s) {
		if (*s == '\'')
			fputs("'\"'\"'", stdout);
		else
			putchar(*s);
	}
	fputs("'\n", stdout);
}


/*
 * Output a TCL assignment in format:  set name "value"
 *
 * If "-v xxx" was specified, do:  set xxx(name) "value"
 *
 */
void emit_assignment_tcl(name, value)
char *name, *value;
{
	char *s;

	/* no special initialize or finish action required */
	if (name == NULL)
		return;

	if (Var_prefix == NULL)
		printf("set %s \"", name);
	else
		printf("set %s(%s) \"", Var_prefix, name);
	for (s = value ; *s != '\0' ; ++s) {
		if (strchr("\"\\[]$", *s) != NULL)
			putchar('\\');
		putchar(*s);
	}
	fputs("\"\n", stdout);
}


/*
 * Output a perl assignment in format:  $name='value';
 */
void emit_assignment_perl(name, value)
char *name, *value;
{
	register char *s;

	if (name == NULL) {
		/* ensure "eval" succeeds */
		if (*value == 'f')
			fputs("1;\n", stdout);
		return;
	}

	if (Var_prefix != NULL)
		printf("$%s{'%s'}='", Var_prefix, name);
	else
		printf("$%s='", name);
	for (s = value ; *s != '\0' ; ++s) {
		/* thanks to Eric Prestemon <eric@american.edu> for the fix */
		if (*s == '\'' || *s == '\\')
			putchar('\\');
		putchar(*s);
	}
	fputs("';\n", stdout);
}

