/* QUISP.C - process file w/ quisp markup, in CGI context
 * Copyright 1998-2004 Stephen C. Grubb  (quisp.sourceforge.net)
 * This code is covered under the GNU General Public License (GPL);
 * see the file ./Copyright for details. */

#define QUISP_VERSION "1.28"

#include <stdio.h>
#include <ctype.h>
#include <signal.h>  /* handling of SIGXCPU signal added, scg 7/19/05 */
#include "cgic.h"
#include "tdhkit.h"
#include "quispcgi.h"

extern int DT_checkdatelengths(), TDH_errlogfile(), TDH_sinterp_open(), TDH_sinterp(), TDH_secondaryops(), TDH_reslimits();
extern int SHSQL_allconfig(), SHSQL_readconfig();
extern int exit(), atoi(), system(), chmod(), putenv();
extern int cgiops();


char MRprojdir[MAXPATH] = "";
char MRdoctag[30] = "rtn";
int MRpreliminary = 1;
int MRcontenthtml = 1;
char MRconvflag[MAXVAR]; /* one char per var, 0 = no conversion, L = converted to list using #cgilistvar */
char MRcgipath[MAXPATH] = "";  /* allows PATH to be set (from config file) for cgi environment */

/* FILE *tfp; */

static char buf[BUFLEN+1];
static char **responses;    /* used by customforvect() */
static int responsesj = -1;    /* used by customforvect() */
static char configfile[ MAXPATH ];
static char defaultrtn[128] = "";
static char debugtag[30] = "_DEBUG";
static int allownonprint = 0;
static char errlogfilename[ MAXPATH ];

static int nonprint();
static int log_error();
static int read_quisp_config();
static void cputime_exception(); /* added scg 7/19/05 */


int 
cgiMain() {
int stat;
int i, j;
char *getenv();
char rtnscript[128]; /* name of script */

struct sinterpstate ss;
char data[ MAXITEMS ][ DATAMAXLEN+ 1]; 
char recordid[40]; 

char progname[128];
char *cookies;
char varname[255];
char tok[ DATAMAXLEN+ 1 ];
char errbuf[1024];
int nlines, nerrlines;
FILE *keepfp;
FILE *outfp;


/* ============== */
/* preliminaries */
/* ============== */

signal( SIGXCPU, cputime_exception ); /* handling of SIGXCPU signal added, scg 7/19/05 */
TDH_setvar( "QUISP_VERSION", QUISP_VERSION );
TDH_midriff_flag = 1;  /* so ploticus doesn't wipe out script environment when it does PL_initstatics() */
DT_checkdatelengths( 0 ); /* scg 6/18/03 */
for( i = 0; i < MAXVAR; i++ ) MRconvflag[i] = 0;

/* get cgi program name (without leading path portion).. */
strcpy( progname, "" );
for( i = strlen( cgiScriptName )-1; i >= 0; i-- ) if( cgiScriptName[i] == '/' ) break;
if( i >= 0 ) strcpy( progname, &cgiScriptName[i+1] );
TDH_setvar( "CGIPROG", cgiScriptName );


/* get VAR=val pairs.. (useful only in command line mode..) */
for( i = 1; i < cgiargc; i++ ) {
        strcpy( buf, cgiargv[i] );
        j = 0;
        GL_getseg( tok, buf, &j, "=" );
        TDH_setvar( tok, &buf[j] );
        }


TDH_debugflag = 0;

TDH_errprog( progname );
TDH_errmode( "stderr" ); /* so as not to interfere with http content-type header */

strcpy( rtnscript, "" );
keepfp = NULL;


/* get HTTP_COOKIE if any.. and set variables corresponding to cookie names..  */
cookies = getenv( "HTTP_COOKIE" );
if( cookies != NULL ) {
	i = 0;
	while( 1 ) {  /* for each cookie in string.. */
		GL_getchunk( buf, cookies, &i, "; 	" );
		if( strlen( buf ) < 1 ) break;
		j = 0;
		GL_getseg( varname, buf, &j, "=" );
		if( strlen( varname ) > 28 ) varname[28] = '\0'; /* truncate var name to 28.. */
		if( strlen( &buf[j] ) > VARMAXLEN-2 ) buf[ j + VARMAXLEN-2 ] = '\0'; /* truncate if too long.. */
		stat = TDH_setvar( varname, &buf[j] );  
		}
	}



/* load and process the config file.. */
stat = read_quisp_config( );
if( stat != 0 ) {
	/* can't do a pretty message here because we don't know script dir yet.. */
	printf(  "Content-type: text/html\n\n" );
	printf( "<html><br><br><br><h3>%s error: cannot read config file</h3>\n", progname );
	sprintf( buf, "Cannot read config file (%s)", configfile );
	TDH_setvar( "_ERRORMESSAGE", buf );
	log_error( "none", "", 0, 0 );
	stat = cgiFormStringNoNewlines( debugtag, buf, 80 );
	if( strlen( buf ) > 0 ) goto DIAG_EXIT;
	else exit( 1 );
	}

sprintf( errlogfilename, "%s/logs/web_errorlog", MRprojdir );
TDH_errlogfile( errlogfilename );


/* get the rtn script name */
stat = cgiFormStringNoNewlines( MRdoctag, rtnscript, 127 );
if( stat != cgiFormSuccess || strlen( rtnscript ) < 1 ) {
	stat = TDH_getvar( MRdoctag, rtnscript );   /* try retrieving it as a var.. in case quisp invoked on command line */
	if( stat && strlen( defaultrtn ) > 0 ) strcpy( rtnscript, defaultrtn );
	else if( stat )	{
		strcpy( rtnscript, "errorpage" );
		sprintf( buf, "%s: no return document specified.", progname );
		TDH_setvar( "_ERRORMESSAGE", buf );
		/* use the normal script processor to deliver error page.. */
		}
	}

/* prohibit ".." in paths */
if( GL_slmember( rtnscript, "*..* .* /*" )) {
	if( strlen( defaultrtn ) > 0 ) strcpy( rtnscript, defaultrtn );
	else	{
		strcpy( rtnscript, "errorpage" );
		sprintf( buf, "%s: invalid return path.", progname );
		TDH_setvar( "_ERRORMESSAGE", buf );
		/* use the normal script processor to deliver error page.. */
		}

	}

/* contenthtml */
stat = cgiFormStringNoNewlines( "contenthtml", buf, 80 );
if( strlen( buf ) > 0 ) {
	if( atoi( buf ) == 0 ) MRcontenthtml = 0;
	}


/* start generating the return HTML doc.. */
if( MRcontenthtml ) {
	printf( "Content-type: text/html\n" );
	}

/* formmode */
/* stat = cgiFormStringNoNewlines( "formmode", buf, 80 );
 * if( strlen( buf ) > 0 ) {
 *	TDH_setvar( "formmode", buf );
 *	}
 */

/* get debug var */
stat = cgiFormStringNoNewlines( debugtag, buf, 80 );
if( strlen( buf ) > 0 ) {
	if( atoi( buf ) != 0 ) TDH_debugflag = 1;
	else TDH_debugflag = 0;
	}



/* ============================ */
/* open script for processing.. */
/* ============================ */

RETRY:
stat = TDH_sinterp_open( rtnscript, &ss );
if( stat != 0 ) { 
	if( strcmp( rtnscript, "errorpage" )==0 ) { 
		/* if we reach here then user has not built an errorpage file; do a generic error msg.. */
		TDH_getvar( "_ERRORMESSAGE", buf );
		printf( "\n<html><br><br><br><h3>%s error: %s</h3><br>\n", progname, buf );
		log_error( rtnscript, buf, 0, 0 );
		goto DIAG_EXIT;
		}
	else	{
		sprintf( buf, "%s: page not found.", rtnscript );
		TDH_setvar( "_ERRORMESSAGE", buf );
		strcpy( rtnscript, "errorpage" );
		goto RETRY; /* use the normal script processor to deliver error page.. */
		}
        }

/* check for non-printable characters.. */
for( i = 0; i < 20; i++ ) {
	int c;
	c = getc( ss.sfp[0] );
	if( !( isalnum( c ) || isspace( c ) || ispunct( c ) ) ) {
		sprintf( buf, "%s: not a viewable page.", rtnscript );
		TDH_setvar( "_ERRORMESSAGE", buf );
		strcpy( rtnscript, "errorpage" );
		goto RETRY; /* use the normal script processor to deliver error page.. */
		}
	}
rewind( ss.sfp[0] );
		

/* set script type.. */
sprintf( buf, "quisp:%s", progname );
TDH_setvar( "_PROGNAME", buf );

/* clear out data array.. */
for( i = 0; i < MAXITEMS; i++ ) strcpy( data[i], "" );
 
/* ========================= */
/* loop on lines in script.. */
/* ========================= */

/* printf( "\n" ); */ /* for debugging */

nlines = 0;
nerrlines = 0;
while( 1 ) {
        stat = TDH_sinterp( buf, &ss, recordid, data );

	/* check for non-printable characters (directory?) */
	if( nlines < 5 && nonprint( buf ) && !allownonprint ) {
		sprintf( buf, "%s not displayable", rtnscript );
		TDH_setvar( "_ERRORMESSAGE", buf );
		/* log_error( rtnscript, buf, 0, 0 ); */
		strcpy( rtnscript, "errorpage" );
		goto RETRY;
		}

	nlines++;

	if( nerrlines > 20 ) break; /* safety valve */

	if( stat >= 20 ) {
		sprintf( errbuf, "Script error %d: %s", stat, buf );
		log_error( rtnscript, errbuf, stat, 0 );
		nerrlines++;
		/* break; */ continue;
		}
        else if( stat != SINTERP_MORE ) break;

	strcpy( tok, "" );
	sscanf( buf, "%s", tok );

	if( ss.writefp != NULL ) outfp = ss.writefp;
	else outfp = stdout;


	/* secondary operators.. */
        if( ss.doingshellresult == 0 && ss.sqlbuildi == 0 && ss.doingsqlresult == 0 && tok[0] == '#' ) {
		while( 1 ) {
			stat = TDH_secondaryops( buf, &ss, recordid, data ); /* may call sinterp to get lines..*/
			if( stat >= 20 ) {
				sprintf( errbuf, "Script error (s) %d: %s", stat, buf );
				log_error( rtnscript, errbuf, stat, 0 );
				nerrlines++;
				continue;
				}
        		else if( stat != SINTERP_MORE ) break;
			/* cgi operators and everything else.. sends it out.. */
			stat = cgiops( outfp, buf, &ss );
			if( stat >= 20 ) { log_error( rtnscript, buf, stat, 1 ); nerrlines++; continue; }
			}
		if( stat == SINTERP_END_BUT_PRINT ) {
			stat = cgiops( outfp, buf, &ss );
			if( stat >= 20 ) { log_error( rtnscript, buf, stat, 1 ); nerrlines++; continue; }
			}
		continue;
		}
	stat = cgiops( outfp, buf, &ss );
	if( stat >= 20 ) { log_error( rtnscript, buf, stat, 1 ); nerrlines++; continue; }
	}

/* added this message, since script errors are now hidden.. scg 7/31/05 */
if( nerrlines > 0 ) {
	printf( "\n<br><h3>Warning, error(s) occurred while generating this page. Results may be incomplete or incorrect.</h3>\n" );
	}

/* ================== */
/* finish up */
/* ================== */

if( ss.ifnest > 0 ) err( 1265, "missing #endif", "" );

if( ss.loopnest > 0 ) err( 1266, "missing #endloop", "" );


/* if( strcmp( rtnscript, "errorpage" )==0 ) log_error( rtnscript, "", 0, 0 ); */ 

if( !TDH_debugflag ) exit( 0 );

/* print debugging info at bottom of page.. */
DIAG_EXIT:
printf( "<h4>Debugging information:<br>" );
printf( "Using config file: %s<br>\n", configfile );
printf( "Using scripts directory: %s<br>\n", TDH_scriptdir );
if( cookies == NULL ) printf( "No cookies received.<br>\n" );
else printf( "Received cookies: %s<br>\n", cookies );
printf( "<br>Environment:</h4><pre>" );
fflush( stdout );
system( "set" );
printf( "</pre>" );
 
exit( 0 );
}


/* ======================================================================= */
/* LOG_ERROR - write an error message to error log.
	Note: bad rtndocs invoke "errorpage" which can selectively write
	to the log as needed.
 */
static int
log_error( rtnscript, msg, errno, show )
char *rtnscript;
char *msg;
int errno;
int show;
{
FILE *logfp;
int mon, day, yr, hr, min, sec, stat;
char *who, *getenv();

if( show ) err( errno, msg, "" ); /* inline display of error message */


GL_sysdate( &mon, &day, &yr );
GL_systime( &hr, &min, &sec );

who = getenv( "REMOTE_HOST" );
if( who == NULL ) who = getenv( "REMOTE_USER" );
else if( who[0] == '\0' ) who = getenv( "REMOTE_USER" );
if( who == NULL ) who = "unknown";

logfp = fopen( errlogfilename, "a" );
if( logfp != NULL ) {
	fprintf( logfp, "%02d/%02d/%02d %02d:%02d:%02d page=%s who=%s q=%s r=%s ", 
		yr, mon, day, hr, min, sec, rtnscript, who, cgiQueryString, cgiReferrer );
	stat = TDH_getvar( "_ERRORMESSAGE", buf );
	if( stat != 0 ) strcpy( buf, "" );
	fprintf( logfp, " ---> %s %s", msg, buf );
	fprintf( logfp, "\n" );
	fclose( logfp );
	chmod( errlogfilename, 00644 ); /* error log world readable */
	}
return( 0 );
}



/* ======================================================================== */
/* CUSTOMFORVECT - defines vector objects that app can #for across.
                   quisp scripts can #for across multi-row select boxes */

int
customforvect( result, obj, n )
char *result;
char *obj;
int n;
{
int stat;

if( n == 1 ) {
	strcpy( result, "" );

	/* responsesj indicates if this has been done before: -1 = no, otherwise yes */
	if( responsesj >= 0 ) cgiStringArrayFree(responses); /* free cgic memory */

	stat = cgiFormStringMultiple( obj, &responses);
	if( stat == cgiFormNotFound ) {
		responsesj = -1;
		return( 1 ); /* no results */
		}
	responsesj = 0;
	
	}

if( responsesj < 0 ) return( 2 );    /* error, probably didn't initialize */

if( !responses[ responsesj ] ) {
	cgiStringArrayFree(responses); /* free cgic memory */
	responsesj = -1;
	return( 1 ); /* no more results */
	}
strcpy( result, responses[ responsesj ] );
responsesj++;
return( 0 );
}

/* ================================================= */
/* READ_QUISP_CONFIG - read config file.  
 */
static int
read_quisp_config( )
{
FILE *cfp;
char buf[512];
char tag[80], value[512];
int cpulimitset;
char *cgiprogname, *confvar, *getenv();
int j, stat;

cgiprogname = cgiargv[0];
strcpy( configfile, cgiprogname );
j = strlen( configfile ) -4;
if( GL_smember( &configfile[ j ], ".cgi .exe" )) configfile[ j ] = '\0';
strcat( configfile, ".cnf" );

/* first load TDHKIT items.. */
sprintf( buf, "file=%s", configfile );
stat = TDH_readconfig( buf ); 
#if TDH_DB == 2
if( stat != 0 ) {
	confvar = getenv( "SHSQL_DB" ); /* try this.. */
	if( confvar == NULL ) confvar = getenv( "SHSQL_CONFIG" ); /* try this.. */
	if( confvar != NULL ) stat = SHSQL_allconfig();
	if( stat == 0 ) err( 1250, "note: found config file by checking SHSQL env var..", "" );
	}

else	{
	/* normal procedure.. next load SHSQL items.. */
	stat += SHSQL_readconfig();
	}
#endif

if( stat != 0 ) return( 1 );

/* now load QUISP items.. */
cfp = fopen( TDH_configfile, "r" );
if( cfp == NULL ) return( 1 );

cpulimitset = 0; 

while( fgets( value, 511, cfp ) != NULL ) {

	TDH_value_subst( buf, value, NULL, "", NORMAL, 0 );

	strcpy( tag, "" );
	strcpy( value, "" );
	if( sscanf( buf, "%s %s", tag, value ) < 1 ) continue;
	if( strncmp( tag, "//", 2 )==0 ) continue;

	else if( stricmp( tag, "projectdir:" )==0 ) strcpy( MRprojdir, value ); 
	else if( stricmp( tag, "cgipath:" )==0 ) {
		sprintf( MRcgipath, "PATH=%s", value ); 
		putenv( MRcgipath );
		}
	else if( stricmp( tag, "doctag:" )==0 ) strcpy( MRdoctag, value ); 
	else if( stricmp( tag, "debugtag:" )==0 ) strcpy( debugtag, value ); 
	else if( stricmp( tag, "defaultrtn:" )==0 ) strcpy( defaultrtn, value ); 
	else if( stricmp( tag, "allownonprint:" )==0 ) allownonprint = atoi( value );
	else if( stricmp( tag, "cpulimit:" )==0 ) { TDH_reslimits( "cpu", atoi( value ) ); cpulimitset = 1; }
	}

fclose( cfp );

if( strcmp( TDH_scriptdir, "./" )==0 ) sprintf( TDH_scriptdir, "%s/pages", MRprojdir );

/* if not specified in config file, the following resource limits apply: */
if( !cpulimitset ) TDH_reslimits( "cpu", 20 ); 		  /* 20 seconds max cpu time */


return( 0 );
}

/* ============================ */
/* NONPRINT - check for non-printable chars.. if any found, return */
static int
nonprint( line )
char *line;
{
int i, linechar;

for( i = 0; i < 80; i++ ) {
        linechar = (unsigned char) line[i];
        if( linechar == '\0' ) break;
        if( linechar < 32 || ( linechar > 127 && linechar < 160 )) {
		if( linechar != 9 && linechar != 10 && linechar != 13 ) return( 1 ); 
		}
        }
return( 0 );
}

#if TDH_DB != 2
TDH_readfdf()
{
return( 0 );
}
#endif

/* ============================ */
/* CPUTIME_EXCEPTION - on receipt of a SIGXCPU signal (indicating that cpu resource limit exceeded),
 * print an appropriate html message to stdout and exit.  Added, scg 7/19/05 */
static void cputime_exception()
{
log_error( "check-usage-log", "cpu resource limit exceeded", 1, 1 );
printf( "<center><h2>CPU resource limit exceeded.<br><br>This result required too much CPU time to prepare.</h2>" );
exit( 1 );
}

