/*
 * Copyright (c) 1996-1998
 *	John-Mark Gurney.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *	This product includes software developed by John-Mark Gurney
 * 4. Neither the name of the author nor the names of any co-contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY John-Mark Gurney AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 */

char *rcsid = "$Id: cgi.c,v 1.40 2000/08/11 18:38:30 jmg Exp $";

#include <config.h>

#include "cgi.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>

#ifdef HAVE_PKG_mime
#include <ct.h>
#include <mime.h>
#include <mimemisc.h>
#else
#include <ct.c>
#endif

#define mymalloc(var,size)	((var) = malloc(size), ((var) == NULL) ? (fprintf(stderr, "%s:%d: out of memory on allocating %d bytes for %s\n", __FILE__, __LINE__, (size), #var), exit(1), (void *)1) : (var) )
#define mycalloc(var,size)	((var) = calloc(1, (size)), ((var) == NULL )? (fprintf(stderr, "%s:%d: out of memory on callocating %d bytes for %s\n", __FILE__, __LINE__, (size), #var), exit(1), (void *)1) : (var) )

struct cgivalues {
	char	*name,			/* name is the register name */
		*value;			/* value is the value of the reg */
	struct	cgivalues *next;	/* next is the next in the linklist */
};

struct cgivalueslist {
	struct	cgivalues *head;
	struct	cgivalueslist *next;
};

static struct cgivalues *start = NULL;	/* this is global for the main linklist
					   all fuctions operate on this */
static struct cgivalueslist *holding = NULL; /* this is used to push new
					   entries onto a stack used for
					   saving context */
#ifdef EXIT_REG
static int registered = 0;		/* used to tell if it has resistered
					   with atexit */
#endif

/*	internal function prototypes	*/
static struct cgivalues **cgigetlastinline(void);
					/* returns pointer to NULL in list */
static void cgidealloccgival(void);	/* deallocate start */
static void cgiparsequerystr(char *a);	/* used to parse the query string */
#ifdef HAVE_PKG_mime
void cgiparsemultipart(char *a, int len, char *ct);
#endif
static int cgiinit(void);		/* starts cgi, for first time, auto
					   decodes incoming data */
static char x2c(char *what);
static void unescape_url(char *url);
static char *cwsc(char **a, char *b);	/* copies word to a new str through
					    char */


void
cgidealloccgival(void)
{
	struct cgivalues *a, *b;

	a = start;
	while (a != NULL) {
		free(a->name);
		free(a->value);
		b = a->next;
		free(a);
		a = b;
	}
	start = NULL;
}

int
cgiinit(void)
{
        char *a;
	char *ct;
#ifdef EXIT_REG
	if (!registered) {
		registered=1;		/* mark registered true */
		atexit(cgiclose);	/* and register for cleanup when done */
	}
#endif
	if ((a = getenv("REQUEST_METHOD")) == NULL)
		return -1;

	if (strcmp(a, "GET") == 0 || strcmp(a, "HEAD") == 0) {
		if ((a = getenv("QUERY_STRING")) != NULL)
					/* calls parsestr if QUERY_STRING
					   environment variable exists */
			cgiparsequerystr(a);
		else
			return -1;
	} else if (strcmp(a, "POST") == 0) {
		int b;
		int i, off;

		if ((ct = getenv("CONTENT_TYPE")) == NULL || (
#ifdef HAVE_PKG_mime
		    (ct_cmpct(ct, "multipart/form-data") != 0) &&
#endif
		    (ct_cmpct(ct, "application/x-www-form-urlencoded") != 0)))
			return -1;

		if ((a = getenv("CONTENT_LENGTH")) == NULL)
			return -1;

		b = atoi(a);
		mymalloc(a, sizeof(char) * (b + 1));
		off = 0;
		while (off < b && (i = read(STDIN_FILENO, (void *)a + off,
		    sizeof(char) * b - off)) != -1 && i != 0)
			off += i;
		a[b] = '\0';

		if (ct_cmpct(ct, "application/x-www-form-urlencoded") == 0)
			cgiparsequerystr(a);
#ifdef HAVE_PKG_mime
		else if (ct_cmpct(ct, "multipart/form-data") == 0)
			cgiparsemultipart(a, b, ct);
#endif

		free(a);
	} else
		return -1;
	return 0;
}

void
cginewquerystr(char *a)
{
	cgidealloccgival();		/* make sure we have a clean start */
	cgiparsequerystr(a);		/* parse the user supplied string */
}

const char *
cgigetvalue(const char *a)
{
	struct cgivalues *b;

	if (start == NULL) {		/* only NULL when first time call
					    or when it was deallocated */
		if (cgiinit())		/* start it off */
			return 0;
	}
	b = start;
	while (b != NULL) {
		if (strcmp(b->name, a) == 0)
			return b->value;
		b = b->next;
	}
	return 0;
}

void
cgiclose(void)
{
	cgidealloccgival();
	cgiclearstack();
}

void
cgiclear(void)
{
	cgidealloccgival();
}

/*
 * The following two fuctions are:
 * Copyright (c) 1995 The Apache Group.  All rights reserved.
 */

char x2c(char *what)
{
	register char digit;

	digit = (what[0] >= 'A' ? ((what[0] & 0xdf) - 'A')+10 : (what[0] - '0'));
	digit *= 16;
	digit += (what[1] >= 'A' ? ((what[1] & 0xdf) - 'A')+10 : (what[1] - '0'));
	return(digit);
}

void unescape_url(char *url)
{
	register int x,y;

	for(x=0,y=0;url[y];++x,++y)
	{
		if((url[x] = url[y]) == '+')
			url[x] = ' ';
		else				/* added this if to replace the
						   +'s with spaces as per spec
						*/
		if((url[x] = url[y]) == '%')
		{
			url[x] = x2c(&url[y+1]);
			y+=2;
		}
	}
	url[x] = '\0';
}

struct cgivalues **
cgigetlastinline(void)
{
	struct cgivalues **a;

	a = &start;
	while (a[0] != NULL)
		a = &(a[0]->next);
	return a;
}

#ifdef HAVE_PKG_mime
void
cgiparsemultipart(char *a, int len, char *ct)
{
	struct cgivalues **d;
	struct cgivalues *e;
	struct attrib *attrib;
	struct mime_message **parts;
	int nparts;
	const char *cd;
	int i;
	int rlen;

	d = cgigetlastinline();

	attrib = mime_getattrib(ct);
	parts = mime_parsemultipart(a, len, attrib_get(attrib, "boundary",
	    NULL), &nparts, NULL, "\r\n");
	attrib_free(attrib);


	for (i = 0; i < nparts; i++) {
		cd = mime_getvalue(mime_getmsgheaders(parts[i]),
		    "content-disposition");
		attrib = mime_getattrib(cd);

		*d = (mycalloc(e, sizeof(struct cgivalues)));
		
		e->name = strdup(attrib_get(attrib, "name", NULL));
		e->value = malloc(mime_estimaterawsize(parts[i]) + 1);
		rlen = mime_getrawbody(parts[i], e->value, 2);
		e->value[rlen] = '\0';
		
		d = &(e->next);
	}
}
#endif

void
cgiparsequerystr(char *a)
{
	struct cgivalues **d;
	struct cgivalues *e;
	char *f, *orig;

	orig = f = strdup(a);

	d = cgigetlastinline();

	while (*f != '\0') {
		*d = (mycalloc(e, sizeof(struct cgivalues)));
		
		e->name = cwsc(&f, "=");
		unescape_url(e->name);
		
		e->value = cwsc(&f, "&;");
		unescape_url(e->value);
		
		d = &(e->next);
	}
	free(orig);
}

char *
cwsc(char **a, char *b)
{
	int c;
	char *d, *e, *pos;

	e = *a;
	pos = e + (c = strcspn(e, b));

	mymalloc(d, sizeof(char) * (c + 1));

	strncpy(d, e, c);
	d[c] = '\0';

	if (*pos != '\0')	/* see if we are ending at a nul */
		*a = pos + 1;	/* if not, point at next char */
	else
		*a = pos;	/* if so, then point at nul */

	return d;
}

int
cgidumpentry(FILE *a, char **entries)
{
	int ts, ret;
	char *c, *d, **b;

	ts = 0;
	b = entries;
	while (*b != NULL) {
		if (cgigetvalue(*b))
			ts += strlen(cgigetvalue(*b))+1;
		else
			ts += 1;
		b++;
	}

	d = mymalloc(c, sizeof(char) * ts);
	b = entries;

	while (*b != NULL) {
		if (cgigetvalue(*b) != NULL) {
			strcpy(c, cgigetvalue(*b));
			c[strlen(c)] = '\0';
			c += strlen(cgigetvalue(*b)) + 1;
		} else {
			*c = '\0';
			c += 1;
		}
		b++;
	}

	ret = fwrite((void *)d, ts, 1, a);

	free(d);

	if (ret != 1)
		return -1;
	return 0;
}

char **
cgigetentry(FILE *a, char **b)
{
	int num, buf_size, pos, i;
	char *buf, **ret, **c;

	c = b;
	num = 0;

	if (ungetc(fgetc(a), a) == EOF)		/* used to make sure that it
						   isn't at EOF */
		return 0;

	while (*c++ != NULL)
		num++;

	mycalloc(ret, (num+1) * sizeof(char *));
	
	c = ret;
	mymalloc(buf, sizeof(char) * (buf_size = 4096));

	for(; num > 0; c++, num--) {
		pos = 0;
		while ((i = fgetc(a)) != EOF && i != '\0' && !feof(a)
		    && !ferror(a)) {
			buf[pos++] = i;
			if (pos >= buf_size)
				buf = realloc(buf, buf_size += 4096);
		}
		
		buf[pos] = '\0';		/* terminate string */
		
		mymalloc(*c, strlen(buf) + 1);
		strcpy(*c, buf);
	}

	free(buf);
	return ret;
}

void
cgifreeentry(char **a)
{
	char **b;

	b = a;
	while (*b != NULL) {
		free(*b);
		b++;
	}
	free(a);
	return;
}

int
fhtmlheader(FILE *a)
{
	return fputs("Content-type: text/html\n\n", a);
}

int
htmlheader(void)
{
	return fhtmlheader(stdout);
}

int
mail(char *from, char *to, char *subject, char *body)
{
	FILE *fd;
	struct cgivalues *a;

	a = start;

	if ((fd = popen("/bin/sh", "w")) == NULL) {
		perror("popen /bin/sh");
		exit(1);
	}

	while (a != NULL) {
		char *z, *y;

		z = y = strdup(a->value);
		while (*y != '\x0') {
			if (*y == '\r') {
				*(y++) = '\\';
				*y = '\n';
			}
			y++;
		}

		fprintf(fd, "%s=\"%s\"\n", a->name, z);
		a = a->next;
		free(z);
	}
	
	fputs(
"/usr/sbin/sendmail -t << HOPEFULLY_THIS_WILL_NEVER_BE_IN_A_MESSAGE\n", fd);

	if (from != NULL)
		fprintf(fd, "From: %s\n", from);

	if (to != NULL)
		fprintf(fd, "To: %s\n", to);

	if (subject != NULL)
		fprintf(fd, "Subject: %s\n", subject);

	if (body != NULL)
		fprintf(fd, "\n%s\n.\n", body);

	fputs("HOPEFULLY_THIS_WILL_NEVER_BE_IN_A_MESSAGE\n", fd);

	fputs("exit\n", fd);

	return pclose(fd);
}

void
cgiaddpair(char *name, char *value)
{
	struct cgivalues **a;
	a = cgigetlastinline();

	mycalloc(a[0], sizeof(struct cgivalues));
	a[0]->name = strdup(name);
	a[0]->value = strdup(value);
}

void
cgipushcurrent(void)
{
	struct cgivalueslist *a;

	mymalloc(a, sizeof(struct cgivalueslist));

	a->head = start;
	a->next = holding;
	holding = a;

	start = NULL;
}

int
cgipopcurrent(void)
{
	struct cgivalueslist *a;

	if (start != NULL)
		cgiclear();

	if (holding != NULL) {
		start = holding->head;
		a = holding;
		holding = holding->next;
		free(a);
		return 1;
	} else {
		start = NULL;
		return 0;
	}
}

void
cgirecreate(char **fields, char **data)
{
	if (start != NULL)
		cgiclear();

	while (*fields != NULL) {
		cgiaddpair(*fields, *data);
		fields++;
		data++;
	}
}

int
cgireplace(char *name, char *value)
{
	struct cgivalues *a;

	a = start;

	while (a != NULL && strcmp(name, a->name) != 0)
		a = a->next;

	if (a != NULL) {
		free(a->value);
		a->value = strdup(value);
		return 0;
	} else
		return 1;
}

void
cgiclearstack(void)
{
	struct cgivalues *a;
	a = start;
	start = NULL;
	while (cgipopcurrent() != 0);
	start = a;
}
