/* This script program manages a registry of user entries added via a form
 * the registry is used in conjunction with a template file to generate
 * an HTML document listing all entries.  The template contains an iteration
 * construct for repeating poritions of the template for each record,
 * sustituting the record data into flagged portions of the template.
 *
 * Arugments: Standard WWWEXEC args (script method xlated-url protocol).
 *
 * The path_info for the script is assumed to be the virtual path of
 * the template file.  The template file must begin with an
 * escape field with the following information:
 *
 *     %[$escape registry-file subfield-list html-outfile]
 *
 * where:
 *    registry-file	VMS file specification of indexed file to
 *			hold data.  If file doesn't exist, this script
 *			will attempt to create it.
 *    subfield-list	Names of subfields within each database record.
 *			These names must correspond with the input names
 *			in the submitting form.  The first subfield must
 *			be named key and the second subfield must be named
 *			name.
 *    html-outfile	VMS file specification of html output file to
 *			generate.
 *
 * The names in the subfield list may then be used in the placeholders
 * within a subsequent $readfile field.
 *
 *	[$readfile registry-file sub-template
 *	...
 *	]
 *
 * Within the sub-template can insert fields of a record by specifying
 * %[field-name].
 *
 * The registry file created by this routine has a primary key 40 bytes
 * long which is the upcased form of the name field.
 *
 * Author:	David Jones
 * Date:	29-NOV-1995
 * Revised:	19-NOV-1996		Add delete hack to allow user deletes
 */
#include <stdio.h>
#include <stdlib.h>
#include <ssdef.h>
#include <string.h>
#include <ctype.h>
#include <unixlib.h>
#include <time.h>
#include "template.h"
#include "subaccess.h"
#include "cgilib.h"
#include "scriptlib.h"

struct db_params {
    int state;		/* 0-initial, 1-middle, 2-done */
    int tok_inwhite;	/* Flags whether looking for non-whitespace */
    int tok_count;	/* number of tokens parsed */
    int tok_used;	/* space in token buffer taken */
    char *tok[10];	/* Pointers into tok_buf */
    FILE *html_file;	/* Temporary HTML file */
    char work_name[256];/* Name of temporary file */
    char tok_buf[1024];
};
static char *fdl_string = "\
 FILE; ORGANIZATION indexed; PROTECTION (system:RWED,owner:RWED,group,world);\
  RECORD; CARRIAGE_CONTROL carriage_return; FORMAT variable; SIZE 8192;\
  KEY 0; CHANGES no; PROLOG 3; SEG0_LENGTH 40; SEG0_POSITION 0; TYPE string;\
";

/*
 * define special list for generating form using escape mechanims.
 */
static char *form_names[6] = { "key", "first", "last", "url",  "comment", "year" };
static char frm_year[8], frm_first[128], frm_last[128];
static char frm_url[256], frm_comment[256], frm_key[44];
static char *form_values[6] = { frm_key, frm_first, frm_last, frm_url,
	frm_comment, frm_year };

static int update_user ( subfield_ctx subfield, struct db_params *arg );
static int parse_form ( int key_limit, char **key, char **value );

static int my_out ( struct db_params *arg, char *buf, int length )
{
    int sent, status;
    /*
     * Ignore all received data until my_escape opens the output file.
     */
    if ( arg->state != 2 ) return 1;
    if ( !arg->html_file ) return 0;	/* no output file */
    /*
     */
    while ( length > 0 ) {
        sent = fwrite ( buf, sizeof(char), length, arg->html_file );
	if ( sent <= 0 ) {
	    return 0;		/* write error */
	}
	length = length - sent;
    }
    return 1;
}

static int my_escape ( struct db_params *arg, char *buf, int length )
{
    int i, j, sent, status, subfield_count, content_length;
    char *var;
    char test_url[512];
    struct subfield_context subfield;
    /*
     * collect data until we have enough to process.
     *
     * arg->state:
     *     0	Initial state.
     *     1    Reading parameters in first escape block.
     *     2    Parameters all read, process.
     *     3    Reading edit form, echo to NET_LINK.
     */
printf("my_escape-- state=%d, length=%d (%s)\n", arg->state, length, buf );
    if ( arg->state == 0 ) {
	arg->tok_count = 0;
	arg->tok_used = 0;
	arg->tok_inwhite = 1;
	arg->html_file = (FILE *) 0;
	arg->state = 1;
    } else if ( arg->state == 3 ) {
	if ( buf[length] ) {
	    char save_c;
	    save_c = buf[length];
	    buf[length] = '\0';
	    status = cgi_printf("%s",buf);
	    buf[length] = save_c;
	} else {
	    status = cgi_printf("%s",buf);
	}
	if ( (status&1) == 0 ) return status;
    }
    if ( arg->state > 1 ) return 1;	/* ignore rest */
    for ( i = 0; i < length; i++ ) {
	if ( arg->tok_used >= sizeof ( arg->tok_buf ) ) break;
	if ( arg->tok_inwhite ) {
	    if ( !isspace ( buf[i] ) ) {
		arg->tok_inwhite = 0;
		arg->tok[arg->tok_count] = &arg->tok_buf[arg->tok_used];
		arg->tok_buf[arg->tok_used++] = buf[i];
	    }
	} else {
	    if ( isspace ( buf[i] ) ) {
		arg->tok_inwhite = 1;
		arg->tok_buf[arg->tok_used++] = '\0';
		arg->tok_count++;
		if ( arg->tok_count >= 3 ) {
		    arg->state = 2;
		    break;
		}
	    } else {
		arg->tok_buf[arg->tok_used++] = buf[i];
	    }
	}
    }
    /*
     * If arg-state reaches 2 we have all we need.
     */
    if ( arg->state == 2 ) {
	/*
	 * Count number of commas in field list (tok[1]) and save
	 * first 2 fields.
	 */
	subfield_count = (arg->tok[1][0]) ? 1 : 0;
	for ( i = 0; arg->tok[1][i]; i++ ) if ( arg->tok[1][i] == ',' ) {
	    subfield_count++;
	}
	/*
	 * Attempt to locate the registry.
	 */
	status = subfield_open ( arg->tok[0], "r+", 	/* filename */
		8192,					/* Max records */
		&subfield );
	    printf("Open of '%s' status: %d\n", arg->tok[0], status );
	if ( (status&1) == 0 ) {
	    IFILE *tmp;
	    char head_rec[512];
	    printf("Open of '%s' status: %d\n", arg->tok[0], status );
	    /*
	     * Try to create it via FDL.
	     */
	    status = ifdlcreate ( fdl_string, arg->tok[0], "sys$disk:[]" );
	    if ((status&8192) == 0) printf(  "ifldcreate status: %d, list: %s\n",
		 status, arg->tok[1] );
	    sprintf(head_rec,"%40s%c%s", "", 0, arg->tok[1] );
	    tmp = ifopen ( arg->tok[0], "r+" );
	     ifwrite_rec ( head_rec, strlen(arg->tok[1])+55, tmp );
	    ifclose ( tmp );
	    if ( (status&1) == 1 ) {
		status = subfield_open ( arg->tok[0], "r+", 	/* filename */
			8192,					/* Max records */
			&subfield );
	    }
	    if ( (status&1) == 0 ) return status;
	}
	/*
	 * Process the form data and add a record.
	 */
	var = cgi_info ( "CONTENT_LENGTH" );
	content_length = var ? atoi(var) : 0;
	if ( content_length > 0 ) {	/* form data present */
	    char *url;
	    status = update_user ( &subfield, arg );
	    url = subfield_value ( &subfield, "url" );
	    cgi_printf("Content-type: text/html\n\n");
	    cgi_printf (  "<HTML><HEAD></HEAD><BODY>\n");
	    cgi_printf ( "Registry file has been updated.\n" );
	    if ( url ) cgi_printf ( 
		"Please <A HREF=\"%s\">verify</A> the URL you entered is correct\n", 
		url );
	    cgi_printf ( "</BODY>\n" );
	} else {
	    /*
	     * Build form using default information.
	     */
	    arg->state = 3;

	    status = subfield_read ( &subfield, "9", 0, 1 );
printf("subfield read: %d\n", status );
	    if ( var=subfield_value(&subfield,"last") ) {
		if (strlen(var) < sizeof(frm_last)) strcpy (frm_last,var);
	    }
	    if ( var=subfield_value(&subfield,"first") ) {
		if (strlen(var) < sizeof(frm_first)) strcpy (frm_first,var);
	    }
	    if ( var=subfield_value(&subfield,"url") ) {
		if (strlen(var) < sizeof(frm_url)) strcpy (frm_url,var);
	    }
	    if ( var=subfield_value(&subfield,"comment") ) {
		if (strlen(var) < sizeof(frm_comment)) strcpy (frm_comment,var);
	    }
	    cgi_printf("Content-type: text/html\nmarker: 1\n\n");
#ifdef with_user
	} else {
	    cgi_printf("Content-type: text/html\n\n");
	    arg->state = 3;
#endif
	}
    }
    return 1;
}

/****************************************************************************/
/* Main program.
 *   Arguments:
 *	argv[1]		name of template file.
 */

int main ( int argc, char **argv )
{
    char *template, *username;
    int length, status, i, t_used, t_alloc;
    struct db_params escape_arg;
    /*
     * Initialize cgilib and get template file name.
     */
    status = cgi_init ( argc, argv );
    if ( (status&1) == 0 ) exit ( status );

    template = cgi_info ( "PATH_TRANSLATED" );
    if ( !template ) {
	cgi_printf ( "content-type: text/plain\n" );
	cgi_printf ( "status: 500 bad info\n\nMissing or invalid path info\n");
        cgi_printf ( "Must supply template file name\n");
	exit ( 1 );
    }
    if ( !*template ) {
	cgi_printf ( "content-type: text/plain\n" );
	cgi_printf ( "status: 500 bad info\n\nNull or invalid path info\n");
        cgi_printf ( "Must supply template file name\n");
	exit ( 1 );
    }
/*
    username = cgi_info ( "REMOTE_USER" );
    if ( !username || !*username ) {
	cgi_printf("content-type: text/plain\n\n");
	cgi_printf("Not authorized  to post, be sure that the script is");
	cgi_printf(" protected so you wil be prompted\n");
	exit(1);
    }
*/
    /*
     * Initialize form input values.
     */
    set_default_template_fields ( 6, form_names, form_values );
/*
    if ( strlen(username) < sizeof(frm_username) ) {
	strcpy ( frm_username, username );
    } else {
	strcpy ( frm_username, "" );
    }
*/
    frm_year[0] = '\0';
    frm_first[0] = frm_last[0] = frm_url[0] = frm_comment[0] = '\0';
    /*
     * Scan template file.  Function called will expand the template
     * file and callback my_out and my_escape with the result.
     */
    escape_arg.state = 0;
    status = process_template_file ( template, (template_callback) my_out, 
	&escape_arg, (template_callback) my_escape, &escape_arg );
    printf("status of processing: %d\n", status );
    if ( escape_arg.state < 2 ) {
	/*
	 * Fallback error message.
	 */
	cgi_printf ( "content-type: text/plain\n" );
	cgi_printf ( "status: 500 error\n\n" );
	cgi_printf ( "Error processing template file, final status: %d\n",
		status );
    } else {
	/*
	 * Do post-processing.  Close and rename the html file output.
	 */
	if ( escape_arg.html_file ) {
	    fclose ( escape_arg.html_file );
	    rename ( escape_arg.work_name, escape_arg.tok[2] );
	}
    }
    return status;
}
/****************************************************************************/
/* Read form content and parse.  Return value is number of keywords parsed,
 * -1 for error.
 */
static int parse_form ( int key_limit, char **key, char **value )
{
    int i, j, content_length, length, count, start, finish, flen;
    char *var, *fdata;
    /*
     * Read form content into memory in preparation for parsing.
     */
    count = 0;
    var = cgi_info ( "CONTENT_LENGTH" );
    content_length = var ? atoi(var) : 0;
    
    if ( content_length > 0 ) {
	/*
	 * Allocate buffer and read entire form data into it, forcing final &.
	 */
	fdata = malloc ( content_length+1 );
	if ( !fdata ) return;
	
	length = cgi_read ( fdata, content_length );
    } else {
	/* See if form method was GET by mistake. */
	var = cgi_info ( "QUERY_STRING" );
	if ( var ) {
	    length = strlen ( var );
	    fdata = malloc ( length + 1 );
	    if ( !fdata ) return;
	    strcpy ( fdata, var );
	} else length = 0;
    }
    if ( length <= 0 ) return length;
    /*
     * Parse into keyword/value pairs, ampersands delimit the pairs.
     */
    if ( fdata[length-1] != '&' ) fdata[length++] = '&';
    start = finish = 0;
    for ( i = 0; i < length; i++ ) if ( !fdata[i] || (fdata[i] == '&') ) {
	/*
	 * Value parsed.  Unescape string and look for first equals to
	 * to delimit field name from value.
	 */
	flen = i - start;
	for ( j = start; j < i; j++ ) if ( fdata[j] == '+' ) fdata[j] = ' ';
	net_unescape_string ( &fdata[start], &flen );
	finish = start + flen;
	fdata[finish] = '\0';
	for ( j = start; j < finish; j++ ) if ( fdata[j] == '=' ) {
	    /* Save pointers to pair in callers buffer, terminate strings */
	    key[count] = &fdata[start];
	    fdata[j] = '\0';
	    value[count] = &fdata[j+1];
	    count++;
	    if ( count >= key_limit ) return count;
	} else {
	    /* Make characters in field name upper case */
	    /* fdata[j] = _toupper(fdata[j]); */
	}
	start = i + 1;
    }
    return count;
}
/****************************************************************************/
/* Lookup field and parse form data.
 */
static int update_user ( subfield_ctx subfield, struct db_params *arg )
{
    int i, j, k, status, input_count, is_new, is_delete;
    char *keyword[64], *value[64], *oldval, *username;
    char ndx_key[44], now[40];
    time_t date;
    /*
     * Parse form data into keyword/value pairs.
     */
    input_count = parse_form ( 64, keyword, value );
    if ( input_count < 0 ) {
	cgi_printf("Content-type: text/plain\nstatus: 500 bad form-data\n\n");
	cgi_printf("Invalid form data\n");
	subfield_close ( subfield );
	exit ( 20 );
    }
    /*
     * locate first and last name fields an combine to index key.
     * Also force to lower case the fields.
     */
    for ( i = 0; i < 40; i++ ) ndx_key[i] = ' '; ndx_key[40] = '\0';
    for ( i = 0; i < input_count; i++ ) {
	if ( strcmp(subfield->fld[1].name, keyword[i]) == 0 ) {
	     /* Copy last name and pretty it */
	     for ( k = 0; value[i][k]; k++ ) {
		if ( k == 0 ) value[i][k] = _toupper ( value[i][k] );
		else value[i][k] = _tolower ( value[i][k] );
		if ( k < 30 ) ndx_key[k] = _toupper ( value[i][k] );
	     }
	     /* Fixup mixed case prefixes */
	     if ( strncmp ( value[i],"Mc", 2 ) == 0 )
		value[i][2] = _toupper ( value[i][2] );
	     
	} else if ( strcmp(subfield->fld[2].name, keyword[i]) == 0 ) {
	     /* Copy first name (and others) */
	     int make_low;
	     for ( make_low = k = 0; value[i][k]; k++ ) {
		if ( !make_low ) value[i][k] = _toupper ( value[i][k] );
		else value[i][k] = _tolower ( value[i][k] );
		make_low = !isspace(value[i][k]);
		if ( k < 10 ) ndx_key[k+30] = _toupper ( value[i][k] );
	     }
	}
    }
    if ( ndx_key[0] == ' ' ) ndx_key[0] = '1';
    /*
     * See if key already present.
     */
    /* username = cgi_info ( "REMOTE_USER" ); */
    status = subfield_read ( subfield, ndx_key, 0, strlen(ndx_key) );
    if ( (status&1) == 1 ) {
	is_new = 0;
#ifndef DEBUG
	printf("ndx_key: '%s'\n", ndx_key );
	for ( i=0; i < subfield->item_count; i++ )
	   printf("old[%d].name = '%s',  .value = '%s'\n", i,
		subfield->fld[i].name, subfield->fld[i].value );
#endif
	/* Name already in use */
    } else {
	is_new = 1;
    }
    /*
     * Scan form data and replace named fields.
     */
    is_delete = 0;
    for ( i = 0; i < input_count; i++ ) {
printf("form keyword: %s\n", keyword[i] );
	if ( strcmp ( keyword[i], "delete" ) == 0 ) {
	    /* Magic field, Delete record */
	    is_delete = 1;
	    break;
	}
	oldval = subfield_value ( subfield, keyword[i] );
	if ( !oldval ) {
	    /* invalid field in form */
	} else {
	    subfield_set ( subfield, keyword[i], value[i] );
	}
    }
    /*
     * Set special fields.
     */
    /* status = subfield_set ( subfield, subfield->fld[0].name, username ); */
    status = subfield_set ( subfield, subfield->fld[0].name, ndx_key );
    if ( subfield_value ( subfield, "date" ) ) {
	/* special field, set to current date */
	time ( &date );
	strcpy ( now, ctime(&date) );
	subfield_set ( subfield, "date", now );
    }
#ifdef DEBUG
	for ( i=0; i < subfield->item_count; i++ )
	   printf("new[%d].name = '%s',  .value = '%s'\n", i,
		subfield->fld[i].name, subfield->fld[i].value );
#endif
    if ( is_delete ) {
	if ( !is_new ) status = subfield_delete_rec ( subfield );
	else status = 20;
printf("delete record status: %d, is_new: %d\n", status, is_new );
    } else if ( is_new ) status = subfield_write ( subfield );
    else status = subfield_update ( subfield );
    /*
     * Create an output file for an updated HTML file.
     */
    sprintf ( arg->work_name, "%s-%x", arg->tok[2], getpid() );
    arg->html_file = fopen ( arg->work_name, "w" );
printf("Status: %d, Openned html file '%s'\n", status, arg->work_name );
    return 1;
}
