/*
**	w3-msql.c	- 
**
**
** Copyright (c) 1995-96  Hughes Technologies Pty Ltd
**
** Permission to use, copy, and distribute for non-commercial purposes,
** is hereby granted without fee, providing that the above copyright
** notice appear in all copies and that both the copyright notice and this
** permission notice appear in supporting documentation.
**
** This software is provided "as is" without any expressed or implied warranty.
**
**
*/


#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>

#include <common/portability.h>
#include <msql/msql.h>

#include "y.tab.h"
#include "lite.h"
#include "version.h"


#define NUM_HANDLES	50
#ifndef O_BINARY
#  define	O_BINARY	0
#endif

extern	char	*yytext;
extern	char	*contentType;
extern	int	yylineno;
static	int	headerSent = 0;

char	*scriptBuf;

void 	checkAuth();
char 	*HTUnEscape();
void 	sendOkHeader();
void 	runError();
void  	parseError();
int   	yylex();
void  	yyerror();
void  	checkContentType();
sym_t	*castToArray();
char	customHdrBuf[2048],
	customRespBuf[160];
int	customHdrMax = 2048,
	customHdrLen = 0,
	customRespLen = 160;


void checkContentType()
{
        static  int     sent=0;

	sendOkHeader();
        if (!sent)
        {
		if (customHdrLen > 0)
		{
			printf(customHdrBuf);
		}
                if(!contentType)
                {
                        printf("Content-type: text/html\n\n");
                }
                else
                {
                        printf("Content-type: %s\n\n",contentType);
                }
                sent++;
        }
        fflush(stdout);
}

/**********************************************************************
** Error routines
**
*/

void parseError(msg)
	char	*msg;
{
	checkContentType();
	printf("</h1></h2></h3></h4></h5></h6>\n");
	printf("</select></ul></dl></ol></table>\n");
	printf("<h3><PRE>\n");
	printf("\n\nW3-mSQL Error!  -  %s\n\n",msg);
	printf("Error at line %d\n\n</PRE></H3>\n",yylineno);
	fflush(stdout);
}

void runError(msg)
	char	*msg;
{
	char	*file;
        extern  char *contentType;

        contentType = NULL;
	checkContentType();
	file = (char *)simGetFileName();
	printf("</h1></h2></h3></h4></h5></h6>\n");
	printf("</select></ul></dl></ol></table>\n");
	printf("<h3><PRE>\n");
	printf("\n\nW3-mSQL Runtime Error!  -  %s\n\n",msg);
	if (!file)
		printf("Error at line %d\n\n</PRE></H3>\n",simGetLineNum());
	else
		printf("Error at line %d of %s\n\n</PRE></H3>\n",
			simGetLineNum(), file);
	fflush(stdout);
}


void yyerror(s)
	char	*s;
{
	char	errBuf[160];

	sprintf(errBuf,"%s near \"%s\"",s ,yytext?yytext:"");
	parseError(errBuf);
	fflush(stdout);
	exit(0);
}




void storeArgs(query, source)
        char    *query;
	int	source;
{
        char    *cp,
		*cp2,
                var[50],
                *val,
		*tmpVal;
	int	count;
	sym_t	*sym,
		*tmp;


        if (!query)
                return;

	cp = query;
	cp2 = var;
        bzero(var,sizeof(var));
	*cp2++ = '$';
	val = NULL;
        while(*cp)
        {
                if (*cp == '=')
                {
                        cp++;
			*cp2 = 0;
                        val = cp;
                        continue;
                }
                if (*cp == '&')
                {
			*cp = 0;
			sym = symGetSymbol(var);
			if (sym)
			{
				/* 
				** Hmmm, it's already there!  Must be
				** data from a multi-select widget.
				** Turn it into an array.
				*/
				if (sym->array == SCALAR)
				{
					sym = castToArray(sym);
				}
				count = symGetNumArrayElements(sym);
				tmp = symCreateSymbol("",TYPE_CHAR, SCALAR);
				symSetArrayElement(sym,count,tmp);
				sym = tmp;
			}
			else
			{
				sym = symCreateSymbol(var,TYPE_CHAR, SCALAR);
			}
			tmpVal = HTUnEscape(val);
			symStoreValue(sym,tmpVal,strlen(tmpVal),TYPE_CHAR);
			sym->source = source;
			sym->length = strlen(sym->val) + 1;
                        cp++;
                        cp2 = var;
			*cp2++ = '$';
			val = NULL;
                        continue;
                }
		if (val)
		{
			cp++;
		}
		else
		{
                	*cp2 = *cp++;
			if (*cp2 == '.')
			{
				strcpy(cp2,"_dot_");
				cp2 += 5;
			}
			else
			{
				cp2++;
			}
		}
        }
	sym = symGetSymbol(var);
	if (sym)
	{
		/* 
		** Hmmm, it's already there!  Must be
		** data from a multi-select widget.
		** Turn it into an array.
		*/
		if (sym->array == SCALAR)
		{
			sym = castToArray(sym);
		}
		count = symGetNumArrayElements(sym);
		tmp = symCreateSymbol("",TYPE_CHAR, SCALAR);
		symSetArrayElement(sym,count,tmp);
		sym = tmp;
	}
	else
	{
		sym = symCreateSymbol(var,TYPE_CHAR, SCALAR);
	}
	sym->val = (char *)strdup(HTUnEscape(val));
	sym->source = source;
	sym->length = strlen(sym->val) + 1;
}

/*
	Added by Jeff Staege   April, 1998     (jstaege@thor.ece.umn.edu)
return in buffer the next string up to token from stdin and remove
	token from stdin
returns the number of characters read from stdin
*/
int scanToNextToken(buffer, token, maxbufsize)
	char 	*buffer;
	char 	*token;
	int 	maxbufsize;
{
	char 	*bufp,	/* point at current position in buffer*/
		*tokp,	/* point at current attempted 
				match position in token*/
		inChar;
	int 	templen,
		i,
		out,
		bufsize;

	bufp = buffer;
	tokp = token;
	inChar='a';
	out = 0;
	bufsize=0;
		/* end if you run out of input, you get a match,
			or you run out of buffer space */
	while(inChar!=EOF &&  bufsize < maxbufsize - 1) 
	{ 
		inChar=getchar();
		if(inChar == EOF) continue;
		out++;		
		if(inChar != *tokp) 
		{		/* current input doesn't match token */
			if(tokp != token) 
			{		/* end partial match */
				templen = tokp - token;	
					/* copy failed match to buffer */
				for(i=0;i<templen;i++) 
				{ 
					if(bufsize < maxbufsize - 1) 
					{
						*bufp++ = *(token + i);
						bufsize++;
					} 
				} 	/* reset matching pointer */
				tokp = token;
			}
			bufsize++;    /* append this character to buffer*/
			*bufp++ = inChar;
			continue;
		}
		if(inChar == *tokp) /* if this character is next in token */
		{			/* if found full token, quit */
			if(tokp - token + 1 >= strlen(token)) 
			{
				inChar = EOF;
			}
			*tokp++;	/* increment matching pointer */
			continue;
		}
	}		
	*bufp = '\0';    /* finish off buffer with end string */
	return out;
}



/* 	Added by Jeff Staege   April, 1998
  save in file up to token from stdin and trim token 
	if size goes over maxsize without finding token, it just stops
		this is just a safety precaution, so this doesn't end
		up writing a file the size of your memory...
	returns the number of characters read from stdin
	works just like scanToNextToken, but saves it to an open file
*/
int saveToNextToken(file, token, maxsize)
	int  	file;
	char 	*token;
	int 	maxsize;
{
	char 	*tokp,
		inChar;
	int 	templen,
		i,
		out,
		bufsize,
		done=0;

	tokp = token;
	out = 0;
	bufsize=0;
	while(done==0) 
	{ 
		inChar=getchar();
		/* if you run out of input before you hit a token
			string, this thing will keep on going, and reading 
			random memory into the file.  It'll stop when it
			runs into maxsize, though.  It'll corrupt your
			file, but won't crash your system (I think) ;^)
			If it does this, you had a bad form to begin with.
		*/
		if(out++ >= maxsize)
		{
			done=1;
			continue;
		}
		if(inChar != *tokp) /* this char doesn't match token */
		{
				/* end partial match */
			if(tokp != token) 
			{
				templen = tokp - token;	
					/* copy failed match to file */
				write(file, token, templen);
				tokp = token;  /* reset match pointer */
			}
			write(file, &inChar, 1);  /* save this character */
			continue;
		}
		if(inChar == *tokp) /* this char matches token */
		{			/* if found token, quit */
			if(tokp - token + 1 >= strlen(token)) 
			{
				done = 1;
			}
			*tokp++;  /* increment test loc in token */
			continue;
		}
	}		
	return out;
}


/* 		Added by Jeff Staege, 1998
   Process multi-part/form-data input
	Take the query info from stdin and process it.  Save all
	regular variables to char variables, save all mult selects to 
	arrays.	
	Take any file inputs, save them to the /tmp/ directory and	
	return the name of the file it was saved as, as the value
	of the variable.  Filenames are like
	"/tmp/w3msq" + a unique random string
the input looks like
-----------------------------202322225011424
Content-Disposition: form-data; name="usernum"

1000
-----------------------------202322225011424
Content-Disposition: form-data; name="file1"; filename="blank.html"
Content-Type: text/html

<body bgcolor=#FFFFFF>

-----------------------------202322225011424
EOF
*/	

void storeMultipartArgs(source)
	int	source;
{
        char    var[50],
                val[15 * 1024],
		*tmpVal,
		boundary[50],
		buffer[15 * 1024],
		inChar;
	int	count,
		i,
		length,
		contentlength,
		done=0,
		file;
	sym_t	*sym,
		*tmp;


		/* save the length of input... 
			 no saved file will be larger than this */
	contentlength = atoi((char *)getenv("CONTENT_LENGTH"));
	

		/* first line in file should be the boundary string */
	scanf("%s ",boundary); 

	while(done==0)
	{
			/* scan in up to the variable name */
		length = scanToNextToken(buffer, "; name=\"", sizeof(buffer));
		if(length < 30) 
		{
			done=1;	
			continue;
		}		/* scan in the variable name */
		scanToNextToken(buffer, "\"", sizeof(buffer));
		strcpy(var,"$");
		strcat(var,buffer);
		inChar = getchar();
		if(inChar == ';')  /* if the variable is followed by
					; filename="name"
					then there is a file, download it*/
		{	/* this is a file element, save it */
				/* scan up to the file name */
			scanToNextToken(buffer, "filename=\"", sizeof(buffer));	
				/* get the original file name */
			scanToNextToken(val, "\"", sizeof(val));	
				/* scan in the content type */
			if(scanf(" Content-Type: %s ", buffer) > 0)
			{		/* file has type, so save it */
				tmpVal = tempnam("/tmp/", "w3msq");
				strcpy(buffer, tmpVal);
				/* adding the filename from the original
					file caused problems, so just use
					w3msq* as file name */
				/*strcat(buffer, val);*/
				free(tmpVal);
					/* the variable value is the 
						file location */
				strcpy(val,buffer);
					/* save the file as r/w only 
						by owner */
				file = open(buffer, O_WRONLY | O_CREAT |
					O_TRUNC | O_BINARY, 0700);
				if(file < 0) 
				{
					strcpy(val, "");
					scanToNextToken(buffer,boundary,
						sizeof(buffer));
				}
				else 
				{
					/* save the file, but if you 
						don't find the boundary
						stop when the file is
						bigger than the 
						content_length*/
					saveToNextToken(file,boundary,
						contentlength);
					close(file);
				}
			} 
			else
			{ /* if no content-type, then don't save the file*/
				strcpy(val, "");
				scanToNextToken(buffer,boundary,sizeof(buffer));
			}
		}
		else
		{	/* regular (not file) entry */
			scanf("\n");	/* scan in the value */
			scanToNextToken(val,boundary,sizeof(val));
				/* trim off trailing cr */
			val[strlen(val) - 2] = '\0';
		}

		/* convert var, val into symbols.  This is right from
			storeArgs()
		*/			

		sym = symGetSymbol(var);
		if (sym)
		{
			/* 
			** Hmmm, it's already there!  Must be
			** data from a multi-select widget.
			** Turn it into an array.
			*/
			if (sym->array == SCALAR)
			{
				sym = castToArray(sym);
			}
			count = symGetNumArrayElements(sym);
			tmp = symCreateSymbol("",TYPE_CHAR, SCALAR);
			symSetArrayElement(sym,count,tmp);
			sym = tmp;
		}
		else
		{
			sym = symCreateSymbol(var,TYPE_CHAR, SCALAR);
		}
		tmpVal = HTUnEscape(val);
		symStoreValue(sym,tmpVal,strlen(tmpVal),TYPE_CHAR);
		sym->source = source;
		sym->length = strlen(sym->val) + 1;
	}

	return;
}


/* modified by Jeff Staege 1998 */
void parseArgs()
{
        char    *query,
		*method,
		*tmpBuf,
		*content;		
	int	length,
		remain;


	method=(char *)getenv("REQUEST_METHOD");
	if (!method || strcmp(method,"GET") == 0)
	{
		query = (char *)getenv("QUERY_STRING");
		if (query)
		{
			if (*query != 0)
			{
				tmpBuf = (char *)strdup(query);
				storeArgs(tmpBuf,SRC_GET);
				free(tmpBuf);
			}
		}
		return;
	}

	if (strcmp(method,"POST" )==0) 
	{
		content = (char *)getenv("CONTENT_TYPE");
		if (content && strncmp(content,"multipart/form-data",19)==0)
		{
			/* 
			** if this is multi-part, have storeMultipartArgs 
			** parse stdin and create the variable and save
			** any files
			*/
			storeMultipartArgs(SRC_POST);
		}			
		else 
		{	/*
			** otherwise, store stdin to a string, and send it 
			** to StoreArgs to parse it and store variables 
			*/
			length = atoi((char *)getenv("CONTENT_LENGTH"));
			query = (char *) malloc(length + 1);
			remain = length;
			while(remain)
			{
				remain -= read(fileno(stdin), 
					query + length - remain, remain);
			}
			query[length]='\0';
			storeArgs(query,SRC_POST);
			free(query);
		}
	}
}


void sendFooter()
{
	extern	char *contentType;

	if (contentType)
	{
		if (strcmp(contentType,"text/html") != 0)
			return;
	}
	checkContentType();
	if (msqlGetIntConf("w3-msql","footer") == 0)
		return;
	printf("<p><br><br><br><br>\n");
	printf("</dl></ul></ol></table>\n");
	printf("</h1></h2></h3></h4></h5></h6>\n");
	printf("<CENTER><FONT SIZE=-2>W3-mSQL %s by ",SERVER_VERSION);
	printf("<a href=http://www.Hughes.com.au/>");
	printf("Hughes Technologies</a></FONT></CENTER>\n\n");
}


void sendAuthHeader(realm, msg, file)
	char	*realm,
		*msg,
		*file;
{
	int	fd,
		len;
	char	buf[1024];

	if (headerSent)
		return;
	headerSent++;

        printf("Status: 401 Error\n");
        printf("WWW-Authenticate: Basic realm=\" '%s' \"\n",realm);
		checkContentType();

		if (!file)
		{
			printf(
			    "<BODY BGCOLOR=#FFFFFF TEXT=#0606A0 LINK=#0000FF ");
			printf(" VLINK=#0000FF>\n");
			printf("<P><BR><CENTER>");
			printf(
			     "<IMG SRC=/Hughes/graphics/banner.gif><P><BR>\n");
			printf("<H1><I>Access Denied</I></H1>\n");
			printf("<P><BR><H3>%s</H3></CENTER><P>\n",msg);
		}
		else
		{
			fd = open(file, O_RDONLY|O_BINARY, 0);
			if (fd < 0)
			{
				printf("<H2>Authentication failed</H2>\n");
			}
			else
			{
				len = read(fd,buf,1024);
				while(len > 0)
				{
					write(fileno(stdout),buf,len);
					len = read(fd,buf,1024);
				}
				close(fd);
			}
		}
		sendFooter();
	}


	void sendOkHeader()
	{
		if (headerSent)
		return;
	headerSent++;
	if (*customRespBuf)
        	printf("Status: %s\n", customRespBuf);
	else
        	printf("Status: 200 Output follows\n");
	printf("Server: w3-mSQL/2\n");
}



void checkMimeTypes(file, buf, len)
	char	*file,
		*buf;
	int	len;
{
	extern	char *contentType;
	char	*cp,
		*type;

	type = NULL;
	cp = (char *)rindex(file, '.');
	if (cp)
	{
		if (strncmp(cp,".htm",4) == 0 ||
	    	    strcmp(cp,".shtml") == 0 ||
	    	    strcmp(cp,".msql") == 0)
		{
			return;
		}
		if (strcmp(cp,".gif") == 0)
		{
			type = "image/gif";
		}
		if (strcmp(cp,".jpg") == 0)
		{
			type = "image/jpeg";
		}
		if (strcmp(cp,".xbm") == 0)
		{
			type = "image/xbm";
		}
		if (strcmp(cp,".png") == 0)
		{
			type = "image/png";
		}
	}
	else
	{
		/*
		** There's no . so it's probably a directory name
		*/
		return;
	}
	if (!type)
	{
		type = "application/octet-stream";
	}

	printf("Status: 200 Output follows\n");
	printf("Server: w3-mSQL/2\n");
	printf("Content-Type: %s\n\n", type);
	fflush(stdout);
	write(fileno(stdout),buf,len);
	exit(0);
}


int main(argc,argv)
	int	argc;
	char	*argv[];
{
	int	fd,
		dFlag,
		tFlag;
	char	*filename = NULL,
		privateScript[255],
		*cp;
	struct	stat	sbuf;

#if defined(_OS_OS2) || defined(_OS_WIN32)
	callback cb;

	cb.runError = runError;
   	cb.parseError = parseError;
	cb.yylex = yylex;
	cb.yyerror = yyerror;
	cb.yylineno = &yylineno;
	cb.checkContentType = checkContentType;
	modLoadModuleCallbacks( &cb);
	fflush(stdout);
	_fsetmode(stdout, "b");
#endif

#ifdef DEBUG
	yydebug++;
#endif


	tFlag = dFlag = 0;
	if (argc > 1)
	{
		if (strcmp(argv[1], "-d") == 0)
		{
			dFlag++;
		}
		if (strcmp(argv[1], "-t") == 0)
		{
			tFlag++;
		}
	}


	msqlLoadConfigFile(NULL);
	filename = (char *)getenv("PATH_TRANSLATED");
	if (!filename)
	{
		sendOkHeader();
		parseError("Input file name missing!");
		exit(1);
	}

	if (stat(filename,&sbuf) < 0 || 
		msqlGetIntConf("w3-msql","force_private") == 1)
	{
		/* is it a private script? */
		sprintf(privateScript,"%s/www%s",
			(char *)msqlGetCharConf("general","inst_dir"),
			getenv("PATH_INFO"));
		filename = privateScript;
		if (stat(filename,&sbuf) < 0)
		{
			sendOkHeader();
			sprintf(privateScript, "Can't stat script file (%s)",
				getenv("PATH_INFO"));
			parseError(privateScript);
			perror("stat");
			printf("\n\n");
			exit(1);
		}
	}
	scriptBuf = (char *)malloc(sbuf.st_size + 1);
	if (!scriptBuf)
	{
		sendOkHeader();
		parseError("Out of memory");
		printf("\n\n");
		exit(1);
	}
	fd = open(filename,O_RDONLY|O_BINARY,0);
	if (fd < 0)
	{
		sendOkHeader();
		parseError("Can't open input file");
		perror("open");
		printf("\n\n");
		exit(1);
	}
	if (read(fd,scriptBuf,sbuf.st_size) != sbuf.st_size)
	{
		sendOkHeader();
		parseError("Load of script file failed (short read)");
		printf("\n\n");
		exit(1);
	}
	*(scriptBuf+sbuf.st_size) = 0;
	checkMimeTypes(filename, scriptBuf, sbuf.st_size);

        lexInitScanner((u_char *)scriptBuf);
        initSymbolTables();
	initModules();

	/*
	** Change directory to the source of the script
	*/
	cp = (char *)rindex(filename,'/');
	if (cp)
	{
		*cp = 0;
		chdir(filename);
	}
	if (!dFlag && !tFlag)
		checkAuth();

	lseek(fd,0,0);
	yyparse();
	parseArgs();

	if (dFlag)
	{
		dumpCode();
	}
	else
	{
		runCode("main");
		sendFooter();
	}
	exit(0);
}
