/*
 * The Shark CGI Function/0.3: a one line function for C CGI programming.
 * Copyright (C) 2002,2003 Michel Blomgren - shark@zebra.ath.cx
 *
 * http://sharkcgi.sourceforge.net/
 * http://sourceforge.net/projects/sharkcgi/
 *
 * This function is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as published by the
 * Free Software Foundation; either version 2.1 of the License, or (at your
 * option) any later version.
 *
 * This function 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 Lesser General Public License
 * for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this function ["LICENSE" file]; if not, write to the Free
 * Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 
 * 02111-1307  USA
 *
 *
 * NOTE! This function uses putenv(3) to put the variables in the environment.
 * It takes for granted that putenv() aliases (don't copy) the environment
 * string passed to it. I've provided a small program (src/putenvcheck.c) to
 * check if your box aliases or copies the putenv()-string. If it copies, then
 * beware, Shark will malloc(3) memory for each environment variable set up,
 * and if putenv() copies then you'll end-up with twice the amount of allocated
 * memory, all for nothing.
 *
 *
 * The "Shark CGI Function" is referred to as simply "Shark" or "the function"
 * from here on. It's only referred to such because it's more simple, it does
 * not mean that simply "Shark" is the name of this function.
 *
 * Shark was inspired by the Un-CGI
 * (http://www.midwinter.com/~koreth/uncgi.html) by Steven Grimm,
 * koreth@midwinter.com. Steve wrote "Unescape all the fields in a form and
 * stick them in the environment so they can be used without awful
 * machinations". That pretty much sums up what this routine does.
 *
 *
 * Features:
 *
 *	o	Shark handles GET and POST forms by setting up the variable
 *		name and the variable's content, e.g. for a POST form you'll
 *		have the environment variable "sharkp_varname=varcontent" and
 *		for a GET form you'll have the environment variable
 *		"sharkg_varname=varcontent".
 *
 *	o	Shark handles multipart/form-data by extracting all variables
 *		and putting them in the environment (e.g.
 *		"sharkmulti_varname=varcontent"). It extracts files that have
 *		been uploaded and puts them in temporary files, which could
 *		easily be accessed by the "sharkfile_" environment variable
 *		(e.g. "sharkfile_varname=/tmp/shark.temp/upload.4531").
 *
 *	o	It also handles cookies by extracting any type of URL or ";"-
 *		terminated cookie format strings. E.g. cookie strings could be
 *		either "varname=varcontens; varname=varcontens;" or URL-encoded
 *		strings;
 *		"cookievar1=hello&cookievar2=hello+world;" (or both), e.g.
 *
 *	o	It's not necessary for you, the CGI coder, to remove any
 *		temporary files that have been created during a file upload,
 *		Shark will automatically remove any tempfiles that are over 30
 *		seconds old. This check is done every time Shark is executed.
 *
 *	o	In case of error, Shark will report an "Apache-like" error
 *		message to the requester (the end-user).
 *
 *	o	Variables that already exist in the environ will be numbered,
 *		e.g. if variable "sharkp_fruit" is already defined and another
 *		"sharkp_fruit" is about to be defined it will be named
 *		"sharkp_fruit2". This is good for "<select multiple>"
 *		input-fields.
 *
 *
 * Bad features (or "so many good features and no bad ones?"):
 *
 *	o	I have to admit that the multipart/form-data function is
 *		currently too slow, ideas on making it faster is highly
 *		appreciated! Here are some stats...
 *
 *		While imitating a CGI environment and emulating a 970KB upload
 *		of two files, I time(1)'d it and I got these "real" times on
 *		different systems (this is _excluding_ any potential upload
 *		time, which was not emulated):
 *
 *  On an AMD Athlon XP 1800+ running Linux: 0.163s.
 *  On a COMPAQ AlphaServer DS20E 666 MHz (2 processors) running Linux: 0.183s.
 *  On a 375MHz PPC (604ev5 - MachV) running Linux: 0.49s.
 *  On a TI UltraSparc II (BlackBird) running Linux: 0.34s.
 *  On a R220 (SPARC) running SunOS 5.8: 0.5s.
 *  On a dual Intel P3 Xeon 500Mhz running FreeBSD: 0.98s.
 *
 *	o	One idea to make it faster would be to mmap(2) stdin instead of
 *		first allocating enough space with malloc(3) and then reading
 *		stdin into that buffer, which is probably somewhat slower.
 *		Though the major time-consumer is probably shark__locateit()
 *		used by shark__get_filename().
 *
 *
 * Not supported:
 *
 *	o	The "multipart/mixed" format is not supported yet, sorry!
 *
 *
 * Comments could be emailed to shark@zebra.ath.cx
 *
 *			/ Michel Blomgren, Dec 28 2002
 *
 *
 * Usage:	Add an extern definition in your main C file, either globally
 *		or under main() or where ever (e.g. "extern int shark();").
 *
 * Example C code:
 *
 *-----------------------------------------------------------------------------
 * #include <stdio.h>
 * #include <stdlib.h>
 * #include <unistd.h>
 * #include <sys/wait.h>
 *
 * extern int shark();
 * 
 * int main()
 * {
 *	char *program = "env";
 *	int pid;
 * 
 *	// a simple way to call shark
 *	// shark() always return 0 if OK, 1 if error occured...
 *
 *	if (shark())
 *		return(1);
 *
 *	printf("Content-type: text/plain\n\n");
 *	fflush(stdout);
 *
 *	pid = fork();
 *
 *	if (pid == 0)
 *	{
 *		execlp(program, program, NULL);
 *		printf("error executing %s!\n", program);
 *		return(-1);
 *	}
 *
 *	wait(NULL);
 *
 *	return 0;
 * }
 * ----------------------------------------------------------------------------
 *
 *
 * How to compile:
 *
 *		Shark compiles successfully with GCC, the following are known
 *		to work without any problem:
 *
 *		gcc version 2.95.3 20010315 (release)
 *		gcc version 2.95.4 20020320 [FreeBSD]
 *		gcc version 2.96 20000731 (Red Hat Linux 7.3 2.96-112)
 *
 *		You can not use Sun's "ucbcc"! Why? Because the d_name in the
 *		dirent structure starts 2 bytes off, so dp->d_name return a
 *		pointer where the first two characters of each file name is
 *		missing. If ucbcc didn't have this "bloat", Shark compiles and
 *		work correctly (proven and tested) with "ucbcc: Sun WorkShop 6
 *		update 1 C 5.2 2000/09/11".
 *
 *		Compilation example ("$" is your prompt):
 *
 *		$ gcc -Wall -s -O3 shark.c yourcgi.c -o yourcgi.cgi
 *
 *		A big THANK YOU goes to SourceForge.net for their Compile Farm
 *		feature. Without access to the Compile Farm servers this
 *		function wouldn't have been as portable as it currently is.
 *
 *
 * A few tips:
 *
 *	o	Don't do a 'printf("Content-type: text/html\n\n");'
 *		before calling shark(); first. shark(); will, in case
 *		of error, print a html error message.
 *
 *	o	Avoid using "international" characters in variable names
 *		(e.g. '<input name=sökfält ....'). It's not "considered"
 *		standard in the environment to have those chars in the vars,
 *		but "technically" there are no problems what so ever.
 *
 *
 */



/*
 * Here I list all functions in Shark that I've included (so far), some of
 * which you might find useful in your C cgi. Just copy this part and put it
 * into your C source. You'll find a thorough documentation of these functions
 * in the README-file.
 *

extern int shark(void);

extern int makelowercase(char *);
extern int makeuppercase(char *);
extern void unescape(char *);
extern char *findstringci(char *, char *);
extern void removechar(char *, unsigned int);
extern char *findzero(char *);
extern void removelastlf(char *);
extern char *percentencode(char *);
extern int cat(char *, FILE *);
extern void namealize(char *);
extern void truncatebuf(char *, unsigned int);
extern char *mallocfile(char *, unsigned int *);
extern char *getarg(char *, unsigned int delim, unsigned int arg);
extern char *gethttpvar(char *src, char *find);
extern void removedoubles(char *, unsigned int c);
extern void replacechar(char *srcstr, unsigned int from, unsigned int to);
extern int mknicefilename(char *);
extern char *sharksig(void);
extern char *sharkhtmlsig(void);
extern char *sharksig2(void);
extern void foreachline(FILE *strm, int(*function)(char *line, FILE *strm));
extern int checkemail(char *emailaddress);
extern int initsrand(void);
extern char *generatepasswd(void);
extern int rmrf(char *dir);

*/



/*****************************************************************************/
/* includes */
/*****************************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <dirent.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <ctype.h>
#include <libgen.h>
#include <errno.h>
#include <netdb.h>


/*****************************************************************************/
/* definitions */
/*****************************************************************************/

/* clean_up_interval = number of seconds to keep old tempfiles */
#ifndef clean_up_interval
	#define clean_up_interval 30
#endif


/* Bytes to reserve for "content" temporary buffer.
 * This means that the content (unescaped) in any form-field (e.g. a
 * <textarea>) can only be tempbuffer_size bytes long, anything over that will
 * be truncated.
 */
#ifndef tempbuffer_size
	#define tempbuffer_size 1024*16	/* 16KB */
#endif

/* Maximum bytes to malloc(3) for the putenv(3) string */
#define max_putenvbuffer_malloc tempbuffer_size*2

/* Maximum number of environment variables Shark will allow.
 * max_shark_environment_items * max_putenvbuffer_malloc = The maximum
 * number of bytes that can be allocated for environment variables.
 * 128 is set by default, which means that you can have a POST- or GET-form
 * with a maximum of 128 input-fields. The absolute maximum memory consumption
 * of 128 items (á 32KB each) is 4 MB.
 */
#ifndef max_shark_environment_items
	#define max_shark_environment_items 128
#endif

/* Maximum bytes to malloc(3) for complete method=POST data */
#ifndef max_post_malloc
	#define max_post_malloc 1024*128	/* 128KB */
#endif

/* Maximum bytes to malloc(3) for multipart/form-data content */
#ifndef max_multipart_malloc
	#define max_multipart_malloc 1024*1024*1	/* 1MB */
#endif


/* Maximum number of bytes a line can hold (before truncated) in the
 * foreachline()-function.
 */
#ifndef foreachline_max_linebuf_bytes
	#define foreachline_max_linebuf_bytes 1024*4
#endif



/* used by the shark__take_care_of_multipart() function for the linebuf and for
 * the "variable" buffer, should not be less than 1024 bytes!
 */
#define small_tempbuffer_size 1024

/* also used by that functions, for the boundary buffer */
#define mini_tempbuffer_size 128





/* other definitions */

#define err_universal 1

#define methodurlencoded 1
#define methodcookie 2



/*****************************************************************************/
/* static variables */
/*****************************************************************************/

extern int errno;
extern int h_errno;

const char shark_version[] = "0.3";

static char tempdir[] = "/var/shark.temp";

static char shark_get_prefix[] = "sharkg_";
static char shark_post_prefix[] = "sharkp_";
static char shark_cookie_prefix[] = "sharkcookie_";
static char shark_multipart_prefix[] = "sharkmulti_";
static char shark_file_prefix[] = "sharkfile_";

static char content_urlencoded[] = "application/x-www-form-urlencoded";
static char content_multipart[] = "multipart/form-data";

static int thisdir;


/* since we would have to malloc(3) this amount of memory anyway, it's
 * probably better to reserve it this way instead...
 */
static char variable[small_tempbuffer_size];
static char content[tempbuffer_size];

static char *cookiep;
static char *queryp;
static char *content_type;
static char *content_length;


static char universal_error_message[200];



/*****************************************************************************/
/* functions */
/*****************************************************************************/

char *sharksig(void)
/*
 * This function returns a pointer to an ascii-zero string with the Shark
 * version signature.
 */
{
	static char signature[32];
	snprintf(signature, sizeof(signature), "Shark CGI Function/%s", shark_version);
	return signature;
}

char *sharkhtmlsig(void)
/*
 * This function returns a pointer to an ascii-zero string with the Shark
 * version signature (with html link).
 */
{
	static char signature[128];
	snprintf(signature, sizeof(signature), "<a href=\"http://sharkcgi.sourceforge.net/\" target=\"_blank\">Shark CGI Function/%s</a>", shark_version);
	return signature;
}

char *sharksig2(void)
/*
 * This function returns a pointer to an ascii-zero string with the Shark
 * version signature (with date of compilation).
 */
{
	static char signature[64];
	snprintf(signature, sizeof(signature), "Shark CGI Function/%s (compiled %s)", shark_version, __DATE__);
	return signature;
}





/*****************************************************************************/

int shark__rmrfr(char *dir)
/*
 * This functino is used by rmrf() below.
 */
{
	DIR *dirp;
	struct dirent *dp;
	struct stat statbuf;

	char cwd[1024*4];
	char pwd[1024*4];

	int entrycount = 0;
	int entries = 0;

	if (lstat(dir, &statbuf))
		return -1;

	if (!S_ISDIR(statbuf.st_mode))
	{
		if (unlink(dir))
			return -1;
		else
			return 0;
	}

	if (!getcwd(cwd, sizeof(cwd)))
		return -1;

	if ((dirp = opendir(dir)) == NULL)
		return -1;

	if (chdir(dir))
		return -1;


	while ((dp = readdir(dirp)) != NULL)
	{
		if (dp->d_name == NULL)
			continue;
		if (!strlen(dp->d_name))
			continue;
		if (!strcmp(dp->d_name, ".") || !strcmp(dp->d_name, ".."))
			continue;

		entrycount++;

		if (lstat(dp->d_name, &statbuf))
			return -1;

		if (S_ISDIR(statbuf.st_mode))
		{
			if ((entries = shark__rmrfr(dp->d_name)) < 0)
				return -1;

			if (!entries)
			{
				/* rmdir this dir */
				if (rmdir(dp->d_name) < 0)
					return -1;
				else
				{
					if (entrycount)
						entrycount--;
				}
			}
		}
		else
		{
			/* else, it's a file or symlink (etc.), delete it.
			 */

			if (!getcwd(pwd, sizeof(pwd)))
				return -1;

			if (unlink(dp->d_name))
				return -1;
			else
			{
				if (entrycount)
					entrycount--;
			}
		}
	}

	if (chdir(cwd))
		return -1;

	return entrycount;
}

/*****************************************************************************/

int rmrf(char *dir)
/*
 * "Frontend" function for shark__rmrfr()
 * 
 * Will delete a path in the same manner as "rm -rf path" will. Returns -1
 * if error or if all/some part(s) of a path could not be deleted. Errors
 * occur when a syscall/function fail, e.g. unlink(), rmdir(), chdir(),
 * getcwd(), etc.
 */
{
	static int errorcode;
	struct stat statbuf;
	int dp;

	if (dir == NULL)
		return -1;
	if (!strlen(dir))
		return 0;
	if (!strcmp(dir, "."))
		return -1;

	if (lstat(dir, &statbuf) < 0)
		return -1;

	if (!S_ISDIR(statbuf.st_mode))
	{
		if (unlink(dir))
			return -1;
		else
			return 0;
	}

	if ((dp = open(".", O_RDONLY)) < 0)
		return -1;


	if (!(errorcode = shark__rmrfr(dir)))
	{
		if (rmdir(dir))
		{
			fchdir(dp);
			close(dp);
			return -1;
		}
	}
	else if (errorcode < 0)
	{
		fchdir(dp);
		close(dp);
		return -1;
	}

	if (fchdir(dp))
	{
		close(dp);
		return -1;
	}

	close(dp);

	return 0;
}

/*****************************************************************************/

int checkemail(char *email)
/*
 * Checks whether or not 'email' is an authentic e-mail address. checkemail()
 * will check if the domain exists, but not whether an MX record exist or a
 * responding SMTP connection established.
 *
 * Returns: 0 if 'email' is OK. != 0 if 'email' is not OK.
 *
 */
{
	char *domain;
	int i;
	int atcount = 0;
	struct hostent *hent;

	if (email == NULL)
		return 1;

	if (!strlen(email))
		return 1;

	for (i = 0; email[i] != '\0'; i++)
	{
		if ((email[i] >= '0' && email[i] <= '9') || (email[i] >= 'a' && email[i] <= 'z') || (email[i] >= 'A' && email[i] <= 'Z') || (email[i] == '.') || (email[i] == '_') || (email[i] == '-') || (email[i] == '+'))
			continue;
		else if (email[i] == '@')
			atcount++;
		else
			return 1;
	}

	if (atcount > 1 || atcount < 1)
		return 1;

	if (email[0] == '@')
		return 1;

	if ((domain = strstr(email, "@")) == NULL)
		return 1;

	domain++;

	if (domain[0] == 0)
		return 1;

	if ((hent = gethostbyname(domain)) == NULL)
	{
		if (h_errno != NO_ADDRESS)
			return 1;
	}

	return 0;
}

/*****************************************************************************/

int initsrand(void)
{
	/* An attempt at initializing srand() with a random number without using /dev/random */

	char procfile1[] = "/proc/uptime";
	char procfile2[] = "/proc/meminfo";
	char procfile3[] = "/proc/loadavg";
	char procfile4[] = "/proc/stat";

	struct timeval tv;

	double pre_initval;

	unsigned int initval;

	FILE *file;
	int buf;

	if (gettimeofday(&tv, NULL))
		return -1;

	pre_initval = ((pid_t)getpid()+1.0) * 1.1 * ((long)tv.tv_usec+1.0);
	pre_initval += (long)tv.tv_sec;
	initval = (double)pre_initval;

	if ((file = fopen(procfile1, "r")) != NULL)
	{
		while (!feof(file) && !ferror(file))
		{
			fread(&buf, sizeof(buf), 1, file);
			initval += buf;
		}
		fclose(file);
	}

	if ((file = fopen(procfile2, "r")) != NULL)
	{
		while (!feof(file) && !ferror(file))
		{
			fread(&buf, sizeof(buf), 1, file);
			initval += buf;
		}
		fclose(file);
	}

	if ((file = fopen(procfile3, "r")) != NULL)
	{
		while (!feof(file) && !ferror(file))
		{
			fread(&buf, sizeof(buf), 1, file);
			initval += buf;
		}
		fclose(file);
	}

	if ((file = fopen(procfile4, "r")) != NULL)
	{
		while (!feof(file) && !ferror(file))
		{
			fread(&buf, sizeof(buf), 1, file);
			initval += buf;
		}
		fclose(file);
	}


	srand(initval);

	return 0;
}

/*****************************************************************************/

char *generatepasswd(void)
/*
 *	Generates a password. Returns NULL if strdup() failed.
 *	Before calling this function you must call initsrand().
 */
{
	char buf[16];
	char *passwd;
	char chars[] = "abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ234567892345678923456789";
	int x = 0;
	int len, i;

	len = 5+(int)(3.0*rand()/(RAND_MAX+1.0));

	while (len)
	{
		i = 1+(int)(((strlen(chars)-1)*1.0)*rand()/(RAND_MAX+1.0));

		buf[x] = chars[i]; x++;
		len--;
	}

	buf[x] = '\0';

	passwd = strdup(buf);

	return passwd;
}


/*****************************************************************************/

void foreachline(FILE *strm, int(*function)(char *line, FILE *strm))
/*
 *	Executes a user-specified function for each line in a stream.
 *	If the "function" returns (int) 0 it will continue to call it.
 *	If the "function" return != 0, then it will stop.
 *
 *	foreachline() handles UNIX, DOS and/or Macintosh text files.
 *
 *	e.g.:
 *
 *		int myfunction(char *line, FILE *strm)
 *		{
 *			printf("%s\n", line);	// echo the line to stdout.
 *			return 0;
 *		}
 *
 *		...
 *
 *		foreachline(stdin, myfunction);
 *
 */
{
	char charbuf[1];
	char linebuf[foreachline_max_linebuf_bytes];
	unsigned int lastchar = 0;
	int i = 0;
	int truncate = 0;

	linebuf[0] = 0;

	while (1)
	{
		fread(charbuf, 1, 1, strm);

		if (feof(strm))
			break;

		if (ferror(strm))
		{
			perror("ferror()");
			break;
		}

		if (charbuf[0] == 0x0a || charbuf[0] == 0x0d)
		{
			if ((charbuf[0] == 0x0a && lastchar == 0x0d) || (charbuf[0] == 0x0d && lastchar == 0x0a))
			{
				/* nop */
			}
			else
			{
				if (!truncate)
				{
					if (function(linebuf, strm))
						break;
					linebuf[0] = 0;
				}
			}

			truncate = 0;
			i = 0;
		}
		else
		{
			if (!truncate)
			{
				linebuf[i] = charbuf[0];
				linebuf[i+1] = 0;
				i++;

				if (i >= (sizeof(linebuf)-1))
				{
					if (function(linebuf, strm))
						break;
					linebuf[0] = 0;
					truncate = 1;
				}
			}
		}

		lastchar = charbuf[0];

	}
}

/*****************************************************************************/

char *getarg(char *unixstr, unsigned int delim, unsigned int arg)
/*
 * getarg() return an argument in a string separated with char 'delim'. The
 * returned string should be free()'d.
 *
 * Returns NULL if argument number 'arg' was not found or malloc() didn't
 * behave, else it returns a free()-able ascii-zero string with the argument.
 *
 * examples:
 *
 *	char teststring[] = "hello:world:wassup";
 *	char *pointer;
 *
 *	pointer = getarg(teststring, ':', 2);
 *
 *	printf("%s\n", pointer);	// will return "world"
 *	if (pointer != NULL)		// just in case
 *		free(pointer);
 *	...
 */
{
	unsigned int i = 0;

	char *start;
	char *end;
	unsigned int length;

	char *memp;

	if (unixstr == NULL)
		return NULL;

	if (arg)
		arg--;

	/*
	 * Find the argument we want...
	 */
	while (arg)
	{
		while (unixstr[i] != delim)
		{
			if (unixstr[i] == 0)
				return NULL;
			i++;
		}
		i++;
		arg--;
	}

	/*
	 * Locate the end, then malloc some memory for the string...
	 */
	start = &unixstr[i];

	i = 0;

	while (start[i] != delim && start[i] != 0)
		i++;

	end = &start[i];
	length = end - start;

	if ((memp = malloc(length+1)) == NULL)
		return NULL;

	strncpy(memp, start, length);
	memp[length] = 0;

	return memp;
}

/*****************************************************************************/

char *mallocfile(char *file, unsigned int *filesize_out)
/*
 * This routine is basically a copy of mmap(2), with one exception, it always
 * adds a trailing zero (null, '\0') to the end of the allocated file to
 * ensure that ascii routines never read passed an allocated ascii file.
 *
 * Example usage:	unsigned int filesize;
 *			char *pointer;
 *
 *			pointer = mallocfile("/etc/passwd", &filesize)
 *
 * mallocfile() returns the pointer to the read file (same as malloc(3)) or
 * NULL if error occured (for example; file couldn't be stat'd or open'd). The
 * filesize integer will contain the exact size in bytes of the file (as
 * reported by stat(3); statbuf.st_size).
 *
 */
{

	static char *memp;

	unsigned int size;

	FILE *fp;

	struct stat statbuf;

	if (stat(file, &statbuf))
		return NULL;

	size = statbuf.st_size;

	if (size == 0)
		return NULL;

	fp = fopen(file, "r");
	if (fp == NULL)
		return NULL;

	memp = malloc(size+1);
	if (memp == NULL)
	{
		fclose(fp);
		return NULL;
	}

	if (fread(memp, 1, size, fp) < size)
	{
		fclose(fp);
		return NULL;
	}

	fclose(fp);

	memp[size] = '\0';

	*filesize_out = size;

	return memp;
}

/*****************************************************************************/

char *percentencode(char *string)
/*
 * url-encodes a string, for use in web environment, e.g.
 *
 * char *string is a null-terminated string to urlencode. percentencode() will
 * return a pointer to the url-encoded string (null-terminated), or NULL if
 * malloc() wouldn't allocate maximum space needed to url-encode the string.
 *
 * the pointer returned should be free()'d before calling percentencode()
 * again!
 *
 */
{
	unsigned int length, i, y, z;
	int x = 0;
	static char *memory;

	length = strlen(string);

	if (length)
	{
		if ((memory = malloc(length*3)) == NULL)
			return NULL;
	}
	else
	{
		if ((memory = malloc(1)) == NULL)
			return NULL;
		memory[0] = '\0';
		return memory;
	}


	for (i = 0; string[i] != 0; i++)
	{
		if (string[i] >= '0' && string[i] <= '9')
		{
			memory[x] = string[i]; x++;
			continue;
		}
		else if (string[i] >= 'A' && string[i] <= 'Z')
		{
			memory[x] = string[i]; x++;
			continue;
		}
		else if (string[i] >= 'a' && string[i] <= 'z')
		{
			memory[x] = string[i]; x++;
			continue;
		}
		else if (string[i] == '.' || string[i] == ',' || string[i] == '-' || string[i] == '_')
		{
			memory[x] = string[i]; x++;
			continue;
		}
		else if (string[i] == ' ')
		{
			memory[x] = '+'; x++;
		}
		else
		{
		/* else urlencode it */

			memory[x] = '%'; x++;

			y = string[i];

			z = y;
			z &= 0xF0;
			z >>= 4;
			if (z >= 0 && z <= 9)
				z += 0x30;
			else
			{
				z -= 0x0A;
				z += 0x41;
			}
			memory[x] = z; x++;

			z = y;
			z &= 0x0F;
			if (z >= 0 && z <= 9)
				z += 0x30;
			else
			{
				z -= 0x0A;
				z += 0x41;
			}
			memory[x] = z; x++;

			continue;
		}

	}

	memory[x] = 0;

	return memory;
}


/*****************************************************************************/

int cat(char *filename, FILE *out)
/*
 * cat, kind of like the program cat(1), outputs a file to a stream (could be
 * "stdout" or whatever).
 *
 * cat will return != 0 if an error occured while doing either fopen(),
 * fread(), or fwrite() - "errno" will speak the truth about the error.
 *
 * return codes from this function are as follows:
 *
 *	0 = no error (everything OK).
 *	1 = couldn't open "filename".
 *	2 = couldn't fread().
 *	3 = couldn't fwrite().
 */
{
	char buf[1024];
	unsigned int bytesread;
	FILE *in;

	if ((in = fopen(filename, "r")) == NULL)
		return 1;
	do
	{
		bytesread = fread(buf, 1, sizeof(buf), in);
		if (ferror(in))
			return 2;
		fwrite(buf, 1, bytesread, out);
		if (ferror(out))
			return 3;
		fflush(out);
	} while ( ! feof(in));

	fclose(in);
	return 0;
}

/*****************************************************************************/


void shark__perr(char *message)
{
	fclose(stdin);

printf("Content-Type: text/html\r\n\r\n"
"<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">\n"
"<HTML><HEAD>\n"
"<TITLE>CGI Error!</TITLE>\n"
"</HEAD><BODY>\n"
"<H1>Error!</H1>\n"
"%s!<P>\n"
"<HR>\n"
"</BODY></HTML>\n", message);

}


/*****************************************************************************/

void truncatebuf(char *buf, unsigned int n)
/*
 * null-terminates a buf at offset (buf+n) if it is longer than n-bytes.
 *
 */
{
	if (strlen(buf) > n)
	{
		buf[n] = '\0';
	}
}

/*****************************************************************************/

int makelowercase(char *string)
/*
 * makes all upper case letters lower case in a string.
 *
 * example usage:
 *
 *	char string[] = "TEST STRÄNG";
 *
 *	makelowercase(string);
 *
 */
{
	char *caps = "ABCDEFGHIJKLMNOPQRSTUVWXYZÅÄÖÉÈËÊÍÌÏÎÓÒÔÚÙÜÛÇÝÁÀÂÃÑ";
	char *lows = "abcdefghijklmnopqrstuvwxyzåäöéèëêíìïîóòôúùüûçýáàâãñ";

	unsigned int i, x;

	for (i = 0; string[i] != 0; i++)
	{
		for (x = 0; caps[x] != 0; x++)
		{
			if (string[i] == caps[x])
			{
				string[i] = lows[x];
			}
		}
	}

	return 0;
}

/*****************************************************************************/

int makeuppercase(char *string)
/*
 * makes all lower case letters upper case in a string.
 *
 * example usage:
 *
 *	char string[] = "tEst Sträng";
 *
 *	makeuppercase(string);
 *
 */
{
	char *caps = "ABCDEFGHIJKLMNOPQRSTUVWXYZÅÄÖÉÈËÊÍÌÏÎÓÒÔÚÙÜÛÇÝÁÀÂÃÑ";
	char *lows = "abcdefghijklmnopqrstuvwxyzåäöéèëêíìïîóòôúùüûçýáàâãñ";

	unsigned int i, x;

	for (i = 0; string[i] != 0; i++)
	{
		for (x = 0; lows[x] != 0; x++)
		{
			if (string[i] == lows[x])
			{
				string[i] = caps[x];
			}
		}
	}


	return 0;
}

/*****************************************************************************/

void removedoubles(char *string, unsigned int c)
/*
 * Removes every double character 'c' from an ascii zero string (leaving only
 * one).
 */
{
	unsigned int i, x = 0;
	unsigned int lastchar = 0;

	char *out;

	if (string == NULL)
		return;

	out = string;

	for (i = 0; string[i] != 0; i++)
	{
		if (lastchar != 0)
		{
			if (string[i] == c)
			{
				if (lastchar != c)
				{
					out[x] = string[i];
					x++;
				}
			}
			else
			{
				out[x] = string[i];
				x++;
			}
			lastchar = string[i];
		}
		else
		{
			if (string[i] != c)
			{
				out[x] = string[i];
				x++;
				lastchar = string[i];
			}
		}
	}

	if (&out[x] > string)
	{
		if (out[x-1] == c)
		{
			out[x-1] = 0;
		}
	}
	out[x] = 0;
}


/*****************************************************************************/

void replacechar(char *string, unsigned int from, unsigned int to)
/*
 * strips a null-terminated string from char 'from' and replaces it with 'to'.
 *
 * example usage:
 *
 *	char string[] = "TEST\nSTRÄNG";
 *
 *	replacechar(string, 0x0a, 0x20);
 *
 */
{
	char *dest = string;

	unsigned int x = 0;
	unsigned int i;

	for (i = 0; string[i] != 0; i++)
	{
		if (string[i] == from)
		{
			dest[x] = to;
			x++;
		}
		else
		{
			dest[x] = string[i];
			x++;
		}
	}

	dest[x] = '\0';
}

/*****************************************************************************/

void namealize(char *string)
/*
 * Converts a string like "michel blomgren" (or "MICHel   BLomgren") to
 * "Michel Blomgren".
 */
{
	char *caps = "ABCDEFGHIJKLMNOPQRSTUVWXYZÅÄÖÉÈËÊÍÌÏÎÓÒÔÚÙÜÛÇÝÁÀÂÃÑ";
	char *lows = "abcdefghijklmnopqrstuvwxyzåäöéèëêíìïîóòôúùüûçýáàâãñ";
	char otherokchars[] = "0123456789., ";
	unsigned int i, x, y;
	unsigned int is_first = 1;

	if (string == NULL)
		return;

	replacechar(string, 0x09, 0x20);
	replacechar(string, '-', 0x20);
	replacechar(string, '_', 0x20);
	replacechar(string, 0x0ff, 0x20);


	/* remove 'bad' chars */

	y = 0;

	for (i = 0; string[i] != 0; i++)
	{
		for (x = 0; lows[x] != 0; x++)
		{
			if (string[i] == lows[x])
			{
				string[y] = string[i];
				y++;
			}
		}

		for (x = 0; caps[x] != 0; x++)
		{
			if (string[i] == caps[x])
			{
				string[y] = string[i];
				y++;
			}
		}

		for (x = 0; otherokchars[x] != 0; x++)
		{
			if (string[i] == otherokchars[x])
			{
				string[y] = string[i];
				y++;
			}
		}
	}
	string[y] = 0;

	removedoubles(string, 0x20);
	removedoubles(string, '.');
	removedoubles(string, ',');

	for (i = 0; string[i] != 0; i++)
	{
		if (is_first)
		{
			/* make this char uppercase */
			if (string[i] != 0x20 && string[i] != 0x09 && string[i] != '.' && string[i] != ',' && string[i] != -1)
			{
				for (x = 0; lows[x] != 0; x++)
				{
					if (string[i] == lows[x])
					{
						string[i] = caps[x];
					}
				}
				is_first = 0;
			}
		}
		else
		{
			/* else, make it char lowercase */
			if (string[i] == 0x20 || string[i] == 0x09 || string[i] == '.' || string[i] == ',' || string[i] == -1)
			{
				is_first = 1;
			}
			else
			{
				for (x = 0; caps[x] != 0; x++)
				{
					if (string[i] == caps[x])
					{
						string[i] = lows[x];
					}
				}
			}
		}
	}
}

/*****************************************************************************/

/* htoi() and unescape() below was copied from Steven Grimm's uncgi.
 * Copyright 1994, Steven Grimm <koreth@midwinter.com>
 *
 * They are both slightly modified by me.
 */

unsigned int htoi(char *s)
{
	unsigned int value;
	unsigned int c;

	c = s[0];
	if (isupper(c))
		c = tolower(c);
	value = (c >= '0' && c <= '9' ? c - '0' : c - 'a' + 10) * 16;

	c = s[1];
	if (isupper(c))
		c = tolower(c);
	value += c >= '0' && c <= '9' ? c - '0' : c - 'a' + 10;

	return value;
}

void unescape(char *string)
#define ishex(x) (((x) >= '0' && (x) <= '9') || ((x) >= 'a' && (x) <= 'f') || ((x) >= 'A' && (x) <= 'F'))
{
	unsigned int i;
	unsigned int x = 0;
	char *bufp = string;

	for (i = 0; string[i] != 0; i++)
	{
		if (string[i] == '%' && string[i+1] != 0 && string[i+2] != 0 && ishex(string[i+1]) && ishex(string[i+2]))
		{
			bufp[x] = htoi(&string[i+1]);
			i += 2;
		}
		else if (string[i] == '+')
		{
			bufp[x] = ' ';
		}
		else
		{
			bufp[x] = string[i];
		}

		x++;
	}

	bufp[x] = 0;
}

/*****************************************************************************/

char *gethttpvar(char *src, char *find)
/*
 * gethttpvar() attempts to find a variable in a METHOD=GET or METHOD=POST type
 * of string, if found it will return an unescaped string with it's contents or
 * NULL if not found. The returned pointer can be free()'d.
 *
 */
{
	unsigned int i, x;
	char *memp;
	char *p;

	if (src == NULL || find == NULL)
		return NULL;

	if (!(i = strlen(src)))
		return NULL;
	if (!(i = strlen(find)))
		return NULL;

	if ((p = strstr(src, find)) == NULL)
		return NULL;
	p += i;

	if (p[0] == '=')
		p++;
	else if (p[0] == '&' || p[0] == 0x0A || p[0] == 0x0D || p[0] == '\0')
	{
		if ((memp = malloc(1)) == NULL)
			return NULL;
		memp[0] = '\0';

		return memp;
	}
	else
		return NULL;

	x = 0;
	for (i = 0; (p[i] != '&') && (p[i] != '\0') && (p[i] != 0x0A) && (p[i] != 0x0D); i++)
	{
		x++;
	}

	if (!x)
	{
		if ((memp = malloc(1)) == NULL)
			return NULL;
		memp[0] = '\0';

		return memp;
	}

	if ((memp = malloc(x+1)) == NULL)
		return NULL;

	x = 0;
	for (i = 0; (p[i] != '&') && (p[i] != '\0') && (p[i] != 0x0A) && (p[i] != 0x0D); i++)
	{
		memp[x] = p[i]; x++;
	}

	memp[x] = '\0';

	unescape(memp);

	return memp;
}

/*****************************************************************************/

int shark__parseintoenviron(char *prefix, char *name, char *value)
{
	char buf[small_tempbuffer_size];
	unsigned int i = 1;

	static int putenvcount = 0;
	unsigned int bytestoallocate;

	char *memp;

	snprintf(buf, sizeof(buf), "%s%s", prefix, name);

	while ((getenv(buf)) != NULL)
	{
		i++;
		snprintf(buf, sizeof(buf), "%s%s%u", prefix, name, i);

		if (i > max_shark_environment_items)
		{
			snprintf(universal_error_message, sizeof(universal_error_message), "\"max_shark_environment_items\" (which is set to %u) reached (line number %u in \"%s\")", max_shark_environment_items, __LINE__, __FILE__);
			return err_universal;
		}
	}

	if (putenvcount <= max_shark_environment_items)
	{

		bytestoallocate = strlen(buf) + 1 + strlen(value) + 2;

		if (bytestoallocate > max_putenvbuffer_malloc)
		{
			snprintf(universal_error_message, sizeof(universal_error_message), "Attempted buffer overflow while reading \"multipart/form-data\" content (\"max_putenvbuffer_malloc\" is set to %u bytes) (line number %u in \"%s\")", max_putenvbuffer_malloc, __LINE__, __FILE__);
			return err_universal;
		}

		if ((memp = malloc(bytestoallocate)) == NULL)
		{
			snprintf(universal_error_message, sizeof(universal_error_message), "Unable to malloc() - %s (line number %u in \"%s\")", strerror(errno), __LINE__, __FILE__);
			return err_universal;
		}

		snprintf(memp, bytestoallocate, "%s=%s", buf, value);

		/* puts(memp); */

		if (putenv(memp))
		{
			snprintf(universal_error_message, sizeof(universal_error_message), "Unable to putenv() - %s (line number %u in \"%s\")", strerror(errno), __LINE__, __FILE__);
			return err_universal;
		}

		putenvcount++;

	}
	else
	{
		snprintf(universal_error_message, sizeof(universal_error_message), "Variables exceed \"max_shark_environment_items\" (which is set to %u) (line number %u in \"%s\")", max_shark_environment_items, __LINE__, __FILE__);
		return err_universal;
	}


	return 0;
}


/*****************************************************************************/

int shark__specialunpack1(char *str, char *prefix)
{
	int returncode = 0;
	unsigned int i = 0;
	unsigned int vc, cc;

	while (str[i])
	{
		/* first, get variable name */

		vc = 0;
		while (str[i] != '\0' && str[i] != '=' && str[i] != '&')
		{
			if (str[i] != 0x20 && vc < (sizeof(variable)-1))
			{
				variable[vc] = str[i]; vc++;
			}
			i++;
		}

		if (vc == 0)
		{
			if (str[i] != '\0')
				i++;
			continue;
		}

		variable[vc] = '\0';
		unescape(variable);

		if ((str[i] == '\0') || (str[i] == '&'))
		{
			content[0] = '\0';
		}
		else
		{
			i++;

			/* then, get content */

			cc = 0;
			while (str[i] != '\0' && str[i] != '&')
			{
				if (cc < (sizeof(content)-1))
				{
					content[cc] = str[i]; cc++;
				}
				i++;
			}
			content[cc] = '\0';
			unescape(content);
		}


		/* putenv here */
		returncode = shark__parseintoenviron(prefix, variable, content);

		if (str[i] != '\0')
			i++;
	}


	return returncode;
}

/*****************************************************************************/

int shark__specialunpack2(char *str, char *prefix)
{
	int returncode = 0;
	unsigned int i = 0;
	unsigned int vc, cc;

	while (str[i])
	{
		/* first, get variable name */

		vc = 0;
		while (str[i] != '\0' && str[i] != '=' && str[i] != '&' && str[i] != ';')
		{
			if (str[i] != 0x20 && vc < (sizeof(variable)-1))
			{
				variable[vc] = str[i]; vc++;
			}

			i++;
		}

		if (vc == 0)
		{

			if (str[i] != '\0')
				i++;
			continue;
		}

		variable[vc] = '\0';
		unescape(variable);

		if ((str[i] == '\0') || (str[i] == '&') || (str[i] == ';'))
		{
			content[0] = '\0';
		}
		else
		{
			i++;

			/* then, get content */

			cc = 0;
			while (str[i] != '\0' && str[i] != '&' && str[i] != ';')
			{
				if (str[i] != 0x20 && cc < (sizeof(content)-1))
				{
					content[cc] = str[i]; cc++;
				}
				i++;
			}
			content[cc] = '\0';
			unescape(content);
		}

		/* putenv here */

		returncode = shark__parseintoenviron(prefix, variable, content);

		if (str[i] != '\0')
			i++;
	}


	return returncode;
}



/*****************************************************************************/

int shark__pairup_unescape_and_putenv(char *urlencoded, char *prefix, int method)
{
	if (urlencoded == NULL)
		return 0;

	if (prefix == NULL)
	{
		snprintf(universal_error_message, sizeof(universal_error_message), "\"prefix\" is NULL in \"shark__pairup_unescape_and_putenv()\" (line number %u in \"%s\")", __LINE__, __FILE__);
		return err_universal;
	}

	if (method == methodurlencoded)
	{
		/* string is said to be standard urlencoded */

		return(shark__specialunpack1(urlencoded, prefix));


	}
	else if (method == methodcookie)
	{
		/* string is said to be cookie-urlencoded */

		return(shark__specialunpack2(urlencoded, prefix));

	}
	else
	{
		snprintf(universal_error_message, sizeof(universal_error_message), "\"method\" is not know in \"shark__pairup_unescape_and_putenv()\" (line number %u in \"%s\")", __LINE__, __FILE__);
		return err_universal;
	}

	/* return err_unknown; */	/* statement not reached */
}


/*****************************************************************************/


char *shark__geteachline(char *bufp, char *outbuf, unsigned int n)
{
	unsigned int i = 0;
	int got_OD = 0;

	unsigned int previous = 0;
	static int eof = 0;


	if (eof > 0)
	{
		eof = 0;
		previous = 0;
		return NULL;
	}

	while(bufp[previous+i] != 0x0a && bufp[previous+i] != 0x0d && bufp[previous+i] != '\0')
		i++;

	switch (bufp[previous+i])
	{
		case 0x0d:
			if (bufp[previous+i+1] == 0x0a)		/* see if we got a DOS newline */
			{
				got_OD = 1;
				if (bufp[previous+i+2] == '\0')	/* see if EOF after the DOS 13,10 */
					eof = 1;
			}
			else if (bufp[previous+i+1] == '\0')	/* EOF yet? */
				eof = 1;
			break;
		case 0x0a:
			if (bufp[previous+i+1] == '\0')
				eof = 1;
			break;
		case '\0':
			eof = 1;
			break;
		default:
			break;
	}

	if (i > 0)
	{
		if (i > n)
			memcpy(outbuf, &bufp[previous], n);
		else
			memcpy(outbuf, &bufp[previous], i);

	}

	if (i > n)
		outbuf[n] = '\0';
	else
		outbuf[i] = '\0';

	previous += i + 1 + got_OD;	/* previous = previous + i + 1; */

	return &bufp[previous];
}


/*****************************************************************************/


char *findstringci(char *nullterminatedbuf, char *cmpagainst)
/*
 * case insensitive findstring()
 */
{
	unsigned int i, n;
	char *memp;

	n = strlen(cmpagainst);

	if ((memp = malloc(strlen(nullterminatedbuf)+1)) == NULL)
		return NULL;

	strcpy(memp, nullterminatedbuf);
	makelowercase(memp);

	for (i = 0; memp[i] != 0; i++)
	{
/*
		if (compare(&memp[i], cmpagainst))
*/
		if (!strncmp(cmpagainst, &memp[i], strlen(cmpagainst)))
		{
			free(memp);
			return &nullterminatedbuf[i+n];
		}

	}

	free(memp);
	return NULL;
}


/*****************************************************************************/

void removechar(char *string, unsigned int c)
/*
 * strips a null-terminated string from char c.
 *
 * example usage:
 *
 *	char string[] = "TEST\nSTRÄNG";
 *
 *	removechar(string, 0x0a);
 *
 */
{
	char *dest = string;

	unsigned int x = 0;
	unsigned int i;

	for (i = 0; string[i] != 0; i++)
	{
		if (string[i] != c)
		{
			dest[x] = string[i]; x++;
		}
	}

	dest[x] = '\0';

}

/*****************************************************************************/

int mknicefilename(char *src)
/*
 * Turns a windows/unix filename into a "nice" basename.
 * For use with Internet Exploder when uploaded files' filenames are
 * e.g. "C:\Windows\Däsktöpp\Suckfile.BMP" - which will be simply
 * "suckfile.bmp" when passed through this function.
 *
 * Returns != 0 if an error occured (basename() fucked up), otherwise - if all
 * is well - it returns 0.
 */
{
	unsigned int i = 0;
	char *bname;

	replacechar(src, '\\', '/');
	removechar(src, ':');

	if ((bname = basename(src)) == NULL)
		return -1;

	strcpy(src, bname);

	/* remove bad chars */
	while (src[i])
	{
		if ((src[i] >= '0' && src[i] <= '9') || (src[i] >= 'a' && src[i] <= 'z') || (src[i] >= 'A' && src[i] <= 'Z') || (src[i] == '.') || (src[i] == '_') || (src[i] == '-') || (src[i] == 0x20))
		{
			i++;
		}
		else
		{
			removechar(src, src[i]);
		}
	}

	removedoubles(src, 0x20);
	replacechar(src, 0x20, '_');

	return 0;
}

/*****************************************************************************/

char *findzero(char *buf)
{
	return &buf[strlen(buf)];
}

/*****************************************************************************/

int shark__placemultivariable(char *dest, char *src)
{
	unsigned int maxlen, i;
	unsigned int x = 0;

	maxlen = small_tempbuffer_size;

	for (i = 0; x < maxlen && src[i] != '\0' && src[i] != ';'; i++)
	{
		dest[x] = src[i]; x++;
	}

	dest[x] = '\0';

	removechar(dest, '"');

	return 0;
}

/*****************************************************************************/

int shark__fillincontent(char *src)
{
	char *origp = content;
	char *newp;

	unsigned int maxlen, i;
	unsigned int x = 0;

	newp = findzero(origp);

	maxlen = (tempbuffer_size - 2) - (newp - origp);

	for (i = 0; x < maxlen && src[i] != '\0'; i++)
	{
		newp[x] = src[i]; x++;
	}

	newp[x] = 0x0A; x++;
	newp[x] = '\0';

	return 0;
}

/*****************************************************************************/

char *shark__locateit(char *nullterminatedbuf, char *cmpagainst, char *startofbuf, unsigned int lenofbuf)
{
	unsigned int i, n, sizeleft;

	if (nullterminatedbuf < startofbuf)
		return NULL;

	n = strlen(cmpagainst);

	if (n == 0)
		return NULL;

	sizeleft = (lenofbuf - (nullterminatedbuf - startofbuf)) - (n-1);

	if (sizeleft < 0)
		return NULL;

	for (i = 0; i < sizeleft; i++)
	{
/*
		if (compare(&nullterminatedbuf[i], cmpagainst))
*/
		if (!strncmp(cmpagainst, &nullterminatedbuf[i], strlen(cmpagainst)))
		{
			return &nullterminatedbuf[i+n];
		}
	}

	return NULL;
}



/*****************************************************************************/

char *shark__get_filename(char *memp, unsigned int size, char *filestart, char *startboundary, char *endboundary)
{
	unsigned int bytestowrite, pid;
	char *tempp;

	char *eof;

	char tempfile[mini_tempbuffer_size];
	char templatename[] = "upload.";
	static unsigned int filecount = 0;

	FILE *fp;


	if ((tempp = shark__locateit(filestart, startboundary, memp, size)) != NULL)
	{
		eof = tempp - strlen(startboundary);
	}
	else if ((tempp = shark__locateit(filestart, endboundary, memp, size)) != NULL)
	{
		eof = tempp - strlen(endboundary);
	}
	else
	{
		return NULL;
	}



	if (eof < filestart)
		return NULL;

	bytestowrite = eof - filestart;

	if (bytestowrite)
	{
		/* write non-empty file here */

		pid = getpid();

		if (snprintf(tempfile, sizeof(tempfile), "%s%u%u", templatename, pid, filecount) > sizeof(tempfile))
			return NULL;


		if ((fp = fopen(tempfile, "w+")) == NULL)
			return NULL;

		fchmod(fileno(fp), 0600);


		if (fwrite(filestart, 1, bytestowrite, fp) < bytestowrite)
			return NULL;

		fclose(fp);

		tempp = findzero(tempdir);

		if (tempp == tempdir)
			return NULL;


		if (shark__parseintoenviron(shark_multipart_prefix, variable, content))
			return NULL;


		if (tempp[-1] == '/')
		{
			snprintf(content, tempbuffer_size, "%s%s", tempdir, tempfile);
		}
		else
		{
			snprintf(content, tempbuffer_size, "%s/%s", tempdir, tempfile);
		}


		/* putenv here */

		if (shark__parseintoenviron(shark_file_prefix, variable, content))
			return NULL;


		filecount++;

	}

	return eof+2;	/* return past the 0x0d,0x0a in the prefix string,
			 * not the 'best' way to do it... */
}



/*****************************************************************************/

void removelastlf(char *src)
{
	char *p;

	p = findzero(src);

	if (p != src)
	{
		if (p[-1] == 0x0a)
			p[-1] = '\0';
	}

}

/*****************************************************************************/

int shark__take_care_of_multipart(char *memp, unsigned int size, char *contentline, char *prefix)
{
	int returncode = 0;
	char mp_content_disposition[] = "content-disposition:";
	char mp_content_type[] = "content-type:";
	char mp_name[] = "name=";
	char mp_filename[] = "filename=";

	char mp_multipartmixed[] = "multipart/mixed";
	char mp_boundary[] = "boundary=";

	char *boundaryp;
	char encoded_start_boundary[mini_tempbuffer_size];
	char encoded_end_boundary[mini_tempbuffer_size];
	char start_boundary[mini_tempbuffer_size];
	char end_boundary[mini_tempbuffer_size];


	char boundary_prefix[] = "--";
	char boundary_raw_prefix[] = {0x0d,0x0a,'-','-',0x00};
	char boundary_endprefix_start[] = {0x0d, 0x0a, 0x00};
	char boundary_endprefix_stop[] = {'-', '-', 0x0d, 0x0a, 0x00};

	char linebuf[small_tempbuffer_size];

	unsigned int step = 1;
	int gotfilename = 0;

	char *nextp;
	char *tempp;


	/* get boundary string from contentline */

	if ((boundaryp = findstringci(contentline, mp_boundary)) == NULL)
	{
		snprintf(universal_error_message, sizeof(universal_error_message), "No boundary found in \"multipart/form-data\" header (line number %u in \"%s\")", __LINE__, __FILE__);
		return err_universal;
	}


	if (snprintf(encoded_start_boundary, sizeof(encoded_start_boundary),"%s%s%s", boundary_raw_prefix, boundaryp, boundary_endprefix_start) > sizeof(encoded_start_boundary))
	{
		snprintf(universal_error_message, sizeof(universal_error_message), "Attempted buffer overflow while reading \"multipart/form-data\" content, no more than %u bytes fit in the buffer (line number %u in \"%s\")", sizeof(encoded_start_boundary)-1, __LINE__, __FILE__);
		return err_universal;
	}

	if (snprintf(encoded_end_boundary, sizeof(encoded_end_boundary),"%s%s%s", boundary_raw_prefix, boundaryp, boundary_endprefix_stop) > sizeof(encoded_end_boundary))
	{
		snprintf(universal_error_message, sizeof(universal_error_message), "Attempted buffer overflow while reading \"multipart/form-data\" content, no more than %u bytes fit in the buffer (line number %u in \"%s\")", sizeof(encoded_end_boundary)-1, __LINE__, __FILE__);
		return err_universal;
	}


	if (snprintf(start_boundary, sizeof(start_boundary), "%s%s", boundary_prefix, boundaryp) > sizeof(start_boundary))
	{
		snprintf(universal_error_message, sizeof(universal_error_message), "Attempted buffer overflow while reading \"multipart/form-data\" content, no more than %u bytes fit in the buffer (line number %u in \"%s\")", sizeof(start_boundary)-1, __LINE__, __FILE__);
		return err_universal;
	}

	if (snprintf(end_boundary, sizeof(end_boundary), "%s%s%s", boundary_prefix, boundaryp, boundary_prefix) > sizeof(end_boundary))
	{
		snprintf(universal_error_message, sizeof(universal_error_message), "Attempted buffer overflow while reading \"multipart/form-data\" content, no more than %u bytes fit in the buffer (line number %u in \"%s\")", sizeof(end_boundary)-1, __LINE__, __FILE__);
		return err_universal;
	}



	nextp = memp;

	while((nextp = shark__geteachline(nextp, linebuf, sizeof(linebuf)-1)) != NULL)
	{

		switch (step)
		{
			case 1:
				/* first step, find start_boundary */

				step++;

/*
				if (comparez(linebuf, start_boundary))
*/
				if (!strcmp(start_boundary, linebuf))
				{
					break;
				}
/*
				else if (comparez(linebuf, end_boundary))
*/
				else if (!strcmp(end_boundary, linebuf))
				{
					return 0;
				}

				snprintf(universal_error_message, sizeof(universal_error_message), "Your browser does not seem to be RFC compliant (RFC 2388 and 1867) (line number %u in \"%s\")", __LINE__, __FILE__);
				return err_universal;


			case 2:
				/* step 2: find content-disposition */

				step++;

				variable[0] = '\0';
				content[0] = '\0';

				if (findstringci(linebuf, mp_content_disposition) == NULL)
				{
					snprintf(universal_error_message, sizeof(universal_error_message), "Your browser does not seem to be RFC compliant (RFC 2388 and 1867) (line number %u in \"%s\")", __LINE__, __FILE__);
					return err_universal;
				}

				/* get name */

				if ((tempp = findstringci(linebuf, mp_name)) == NULL)
				{
					snprintf(universal_error_message, sizeof(universal_error_message), "Your browser does not seem to be RFC compliant (RFC 2388 and 1867) (line number %u in \"%s\")", __LINE__, __FILE__);
					return err_universal;
				}

				if (shark__placemultivariable(variable, tempp))
				{
					snprintf(universal_error_message, sizeof(universal_error_message), "Attempted buffer overflow by function \"shark__placemultivariable()\" (line number %u in \"%s\")", __LINE__, __FILE__);
					return err_universal;
				}


				/* see if there's a "filename" too */

				if ((tempp = findstringci(linebuf, mp_filename)) == NULL)
				{
					gotfilename = 0;
					break;
				}

				/* place filename-var in content instead of content, since it's a file */
				if (shark__placemultivariable(content, tempp))
				{
					snprintf(universal_error_message, sizeof(universal_error_message), "Attempted buffer overflow by function \"shark__placemultivariable()\" (line number %u in \"%s\")", __LINE__, __FILE__);
					return err_universal;
				}

				gotfilename = 1;

				break;

			case 3:
				/* step 3: get content-type or empty line */

				step++;


				if ( ! gotfilename)
				{
					/* code for no filename */

					if (strlen(linebuf))
					{
						if ((tempp = findstringci(linebuf, mp_content_type)) == NULL)
							tempp = linebuf;

						if (findstringci(tempp, mp_multipartmixed))
						{
							snprintf(universal_error_message, sizeof(universal_error_message), "This version of the Shark CGI Function (still) does not support \"multipart/mixed\" content, sorry (line number %u in \"%s\")", __LINE__, __FILE__);
							return err_universal;
						}

						if (findstringci(tempp, mp_boundary))
						{
							snprintf(universal_error_message, sizeof(universal_error_message), "This version of the Shark CGI Function (still) does not support \"multipart/mixed\" content, sorry (line number %u in \"%s\")", __LINE__, __FILE__);
							return err_universal;
						}

						snprintf(universal_error_message, sizeof(universal_error_message), "Your browser does not seem to be RFC compliant (RFC 2388 and 1867) (line number %u in \"%s\")", __LINE__, __FILE__);
						return err_universal;
					}

				}
				else
				{
					/* code for a filename */

					if ((tempp = findstringci(linebuf, mp_content_type)) != NULL)
					{
						if (findstringci(tempp, mp_multipartmixed))
						{
							snprintf(universal_error_message, sizeof(universal_error_message), "This version of the Shark CGI Function (still) does not support \"multipart/mixed\" content, sorry (line number %u in \"%s\")", __LINE__, __FILE__);
							return err_universal;
						}

						if (findstringci(tempp, mp_boundary))
						{
							snprintf(universal_error_message, sizeof(universal_error_message), "This version of the Shark CGI Function (still) does not support \"multipart/mixed\" content, sorry (line number %u in \"%s\")", __LINE__, __FILE__);
							return err_universal;
						}

					}
					else
					{
						step = 1;	/* restart at step 1 */

						if (strlen(linebuf))
						{
							snprintf(universal_error_message, sizeof(universal_error_message), "Your browser does not seem to be RFC compliant (RFC 2388 and 1867) (line number %u in \"%s\")", __LINE__, __FILE__);
							return err_universal;
						}

						if ((nextp = shark__get_filename(memp, size, nextp, encoded_start_boundary, encoded_end_boundary)) == NULL)
						{
							snprintf(universal_error_message, sizeof(universal_error_message), "An unknown error occured while \"shark__get_filename()\" returned (line number %u in \"%s\")", __LINE__, __FILE__);
							return err_universal;
						}

					}

				}

				break;


			case 4:
				/* step 4: Get variable and put in content *OR*
					   get filename content and put in a tempfile
					   (and setup an extra var)... */

				if ( ! gotfilename)
				{
					/* code for no filename */

/*
					if (comparez(linebuf, end_boundary))
*/
					if (!strcmp(end_boundary, linebuf))
					{
						/* putenv here */

						removelastlf(content);
						returncode = shark__parseintoenviron(prefix, variable, content);
						if (returncode)
							return returncode;


						return 0;
					}

/*
					if (comparez(linebuf, start_boundary))
*/
					if (!strcmp(start_boundary, linebuf))
					{
						step = 2;	/* restart at step 2 */

						/* putenv here */

						removelastlf(content);
						returncode = shark__parseintoenviron(prefix, variable, content);
						if (returncode)
							return returncode;

					}
					else
					{
						if (shark__fillincontent(linebuf))
						{
							snprintf(universal_error_message, sizeof(universal_error_message), "Attempted buffer overflow by function \"shark__fillincontent()\" (line number %u in \"%s\")", __LINE__, __FILE__);
							return err_universal;
						}
					}


				}
				else
				{
					/* code for a filename */

					step = 1;	/* restart at step 1 */

					if (strlen(linebuf))
					{
						snprintf(universal_error_message, sizeof(universal_error_message), "Your browser does not seem to be RFC compliant (RFC 2388 and 1867) (line number %u in \"%s\")", __LINE__, __FILE__);
						return err_universal;
					}

					if ((nextp = shark__get_filename(memp, size, nextp, encoded_start_boundary, encoded_end_boundary)) == NULL)
					{
						snprintf(universal_error_message, sizeof(universal_error_message), "\"nextp\" was NULL when function \"shark__get_filename()\" returned (line number %u in \"%s\")", __LINE__, __FILE__);
						return err_universal;
					}

				}

				break;


			default:
				snprintf(universal_error_message, sizeof(universal_error_message), "Your browser does not seem to be RFC compliant (RFC 2388 and 1867) (line number %u in \"%s\")", __LINE__, __FILE__);
				return err_universal;

		}

	}



	return 0;
}


/*****************************************************************************/

void shark__take_care_of_error(int errcode)
{
	if (errcode)
		shark__perr(universal_error_message);
}

/*****************************************************************************/

int shark__method_get()
{
	int returncode;
	cookiep = getenv("HTTP_COOKIE");
	queryp = getenv("QUERY_STRING");

	/* unescape and put the QUERY_STRING variables in the environ */
	returncode = shark__pairup_unescape_and_putenv(queryp, shark_get_prefix, methodurlencoded);
	if (returncode)
	{
		shark__take_care_of_error(returncode);
		return 1;
	}

	/* unescape and put any cookie variables in the environ */
	returncode = shark__pairup_unescape_and_putenv(cookiep, shark_cookie_prefix, methodcookie);
	if (returncode)
	{
		shark__take_care_of_error(returncode);
		return 1;
	}

	return 0;
}

/*****************************************************************************/

int shark__method_post()
{
	unsigned int size, bytesread, returncode;

	char *memp;


	cookiep = getenv("HTTP_COOKIE");
	queryp = getenv("QUERY_STRING");

	content_type = getenv("CONTENT_TYPE");
	content_length = getenv("CONTENT_LENGTH");

	if (content_type == NULL)
	{
		shark__perr("No CONTENT_TYPE in the environ");
		return 1;
	}

	if (content_length == NULL)
	{
		shark__perr("No CONTENT_LENGTH in the environ");
		return 1;
	}

/*
	if (compare(content_type, content_urlencoded))
*/
	if (!strncmp(content_urlencoded, content_type, strlen(content_urlencoded)))
	{
		/* content is urlencoded */

		/* unescape and put the QUERY_STRING variables in the environ */
		returncode = shark__pairup_unescape_and_putenv(queryp, shark_get_prefix, methodurlencoded);
		if (returncode)
		{
			shark__take_care_of_error(returncode);
			return 1;
		}

		/* unescape and put any cookie variables in the environ */
		returncode = shark__pairup_unescape_and_putenv(cookiep, shark_cookie_prefix, methodcookie);
		if (returncode)
		{
			shark__take_care_of_error(returncode);
			return 1;
		}


		size = (unsigned) atoi(content_length);
		if (size == 0)
			return 0;	/* nothing to do */

		if (size > max_post_malloc)
			size = max_post_malloc;

		if ((memp = malloc(size+1)) == NULL)
		{
			shark__perr("Error trying to allocate memory");
			return 1;
		}


		bytesread = fread(memp, 1, size, stdin);

		if (ferror(stdin))
		{
			shark__perr("Error trying to read STDIN");
			return 1;
		}
		else if (bytesread == 0)
		{
			free(memp);
			fclose(stdin);
			return 0;
		}

		fclose(stdin);

		memp[bytesread] = '\0';

/*		fwrite(memp, 1, bytesread, fopen("/tmp/compostheap", "w+")); */


		/* unescape and put the POST variables in the environ */
		returncode = shark__pairup_unescape_and_putenv(memp, shark_post_prefix, methodurlencoded);
		if (returncode)
		{
			shark__take_care_of_error(returncode);
			return 1;
		}

		free(memp);
	}
/*
	else if (compare(content_type, content_multipart))
*/
	else if (!strncmp(content_multipart, content_type, strlen(content_multipart)))
	{
		/* content is multipart/form-data */


		/* unescape and put the QUERY_STRING variables in the environ */
		returncode = shark__pairup_unescape_and_putenv(queryp, shark_get_prefix, methodurlencoded);
		if (returncode)
		{
			shark__take_care_of_error(returncode);
			return 1;
		}

		/* unescape and put any cookie variables in the environ */
		returncode = shark__pairup_unescape_and_putenv(cookiep, shark_cookie_prefix, methodcookie);
		if (returncode)
		{
			shark__take_care_of_error(returncode);
			return 1;
		}

		size = (unsigned) atoi(content_length);
		if (size == 0)
			return 0;	/* nothing to do */

		/* printf("content_length: %u\n", size); */

		if (size > max_multipart_malloc)
		{
			snprintf(universal_error_message, sizeof(universal_error_message), "Your \"multipart/form-data\" content is too large, max %.2fK bytes are allowed to be uploaded (line number %u in \"%s\")", max_multipart_malloc/1000.0, __LINE__, __FILE__);
			shark__perr(universal_error_message);
			return 1;
		}


		if ((memp = malloc(size+1)) == NULL)
		{
			snprintf(universal_error_message, sizeof(universal_error_message), "Error trying to allocate memory - %s (line number %u in \"%s\")", strerror(errno), __LINE__, __FILE__);
			shark__perr(universal_error_message);
			return 1;
		}


		bytesread = fread(memp, 1, size, stdin);

		if (ferror(stdin))
		{
			snprintf(universal_error_message, sizeof(universal_error_message), "Error trying to read STDIN stream - %s (line number %u in \"%s\")", strerror(errno), __LINE__, __FILE__);
			shark__perr(universal_error_message);
			return 1;
		}
		else if (bytesread == 0)
		{
			free(memp);
			fclose(stdin);
			return 0;
		}

		fclose(stdin);

		memp[bytesread] = '\0';

/*
		fwrite(memp, 1, bytesread, fopen("/tmp/compostheap", "w+"));
*/

		/* extract variables from multipart/form-data content */

		returncode = shark__take_care_of_multipart(memp, bytesread, content_type, shark_multipart_prefix);

		free(memp);

		if (returncode)
		{
			shark__take_care_of_error(returncode);
			return 1;
		}
	}
	else
	{
		snprintf(universal_error_message, sizeof(universal_error_message), "Could not determine your content type - \"%s\" (line number %u in \"%s\")", content_type, __LINE__, __FILE__);
		shark__perr(universal_error_message);
		return 1;
	}

	return 0;
}


/*****************************************************************************/

int shark__gototempdir_and_cleanup()
{
	struct timeval tv;
	time_t tcmp;

	DIR *dirp;
	struct dirent *dp;
	struct stat statbuf;

	if (gettimeofday(&tv, NULL))
	{
		snprintf(universal_error_message, sizeof(universal_error_message), "\"gettimeofday()\" fucked up - %s (line number %u in \"%s\")", strerror(errno), __LINE__, __FILE__);
		return err_universal;
	}

	tcmp = tv.tv_sec - clean_up_interval;


	/* first we attempt to chdir to tempfile, if that won't happen we attempt to mkdir it */

	if ((dirp = opendir(tempdir)) == NULL)
	{
		if (errno == ENOENT)
		{
			if (mkdir(tempdir, 0700))
			{
				snprintf(universal_error_message, sizeof(universal_error_message), "Error trying to mkdir(\"%s\") - %s (line number %u in \"%s\")", tempdir, strerror(errno), __LINE__, __FILE__);
				return err_universal;
			}

			if (chmod(tempdir, 0700))
			{
				snprintf(universal_error_message, sizeof(universal_error_message), "Error trying to chmod(\"%s\", 0700) - %s (line number %u in \"%s\")", tempdir, strerror(errno), __LINE__, __FILE__);
				return err_universal;
			}

			if ((dirp = opendir(tempdir)) == NULL)
			{
				snprintf(universal_error_message, sizeof(universal_error_message), "Error trying to opendir(\"%s\") - %s (line number %u in \"%s\")", tempdir, strerror(errno), __LINE__, __FILE__);
				return err_universal;
			}
		}
		else
		{
			snprintf(universal_error_message, sizeof(universal_error_message), "Error trying to opendir(\"%s\") - %s (line number %u in \"%s\")", tempdir, strerror(errno), __LINE__, __FILE__);
			return err_universal;
		}
	}


	if (chdir(tempdir))
	{
		snprintf(universal_error_message, sizeof(universal_error_message), "Error trying to chdir(\"%s\") - %s (line number %u in \"%s\")", tempdir, strerror(errno), __LINE__, __FILE__);
		return err_universal;
	}



	/* let's clean-up old tempfiles from the tempdir */

	while ((dp = readdir(dirp)) != NULL)
	{
		if (strlen(dp->d_name))	/* just in case we get a NULL name (SunOS using ucbcc) */
		{
/*
			if (( ! comparez(dp->d_name, ".")) && ( ! comparez(dp->d_name, "..")))
*/
			if (strcmp(dp->d_name, ".") && strcmp(dp->d_name, ".."))
			{
				/* printf("(shit) %s\n", dp->d_name); */

				if (stat(dp->d_name, &statbuf))
				{
					snprintf(universal_error_message, sizeof(universal_error_message), "Error trying to stat() - %s (line number %u in \"%s\")", strerror(errno), __LINE__, __FILE__);
					return err_universal;
				}

				if (statbuf.st_ctime < tcmp)
				{
					if (unlink(dp->d_name))
					{
						snprintf(universal_error_message, sizeof(universal_error_message), "Can't unlink(\"%s\") - %s (line number %u in \"%s\")", dp->d_name, strerror(errno), __LINE__, __FILE__);
						return err_universal;
					}
				}
			}
		}
	}

	closedir(dirp);

	return 0;
}




/*****************************************************************************/
/* main-function */
/*****************************************************************************/

int shark()
{
	int returncode;
	char *request_method;

/*	printf("Content-Type: text/plain\n\nDebug mode!\n\n");
	fflush(stdout); */


	if ((thisdir = open(".", O_RDONLY)) < 0)
	{
		snprintf(universal_error_message, sizeof(universal_error_message), "Unable to open current working directory - %s (line number %u in \"%s\")", strerror(errno), __LINE__, __FILE__);
		shark__perr(universal_error_message);
		return 1;
	}


	returncode = shark__gototempdir_and_cleanup();
	if (returncode)
	{
		shark__take_care_of_error(returncode);
		return 1;
	}


	if ((request_method = getenv("REQUEST_METHOD")) == NULL)
	{
		shark__perr("No REQUEST_METHOD in the environ, are you running through a webserver?");
		return 1;
	}


/*
	if (comparez(request_method, "GET"))
*/
	if (!strcasecmp(request_method, "GET"))
	{
		if (shark__method_get())
			return 1;
	}
/*
	else if (comparez(request_method, "POST"))
*/
	else if (!strcasecmp(request_method, "POST"))
	{
		if (shark__method_post())
			return 1;
	}
	else
	{
		snprintf(universal_error_message, sizeof(universal_error_message), "REQUEST_METHOD \"%s\" not implemented (line number %u in \"%s\")", request_method, __LINE__, __FILE__);
		shark__perr(universal_error_message);
		return 1;
	}

	fchdir(thisdir);
	close(thisdir);

	return 0;
}


/*****************************************************************************/
/* the end of the shark cgi function */
/*****************************************************************************/
